Customized sprintf,is it worth the effort? - c

The code below is extracted from nginx,which basically rewrites sprintf,in fact nginx also rewrites some other string functions,is it worth the effort?
u_char *
ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args)
{
u_char *p, zero;
int d;
double f, scale;
size_t len, slen;
int64_t i64;
uint64_t ui64;
ngx_msec_t ms;
ngx_uint_t width, sign, hex, max_width, frac_width, n;
ngx_str_t *v;
ngx_variable_value_t *vv;
while (*fmt && buf < last) {
/*
* "buf < last" means that we could copy at least one character:
* the plain character, "%%", "%c", and minus without the checking
*/
if (*fmt == '%') {
i64 = 0;
ui64 = 0;
zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
width = 0;
sign = 1;
hex = 0;
max_width = 0;
frac_width = 0;
slen = (size_t) -1;
while (*fmt >= '0' && *fmt <= '9') {
width = width * 10 + *fmt++ - '0';
}
for ( ;; ) {
switch (*fmt) {
case 'u':
sign = 0;
fmt++;
continue;
case 'm':
max_width = 1;
fmt++;
continue;
case 'X':
hex = 2;
sign = 0;
fmt++;
continue;
case 'x':
hex = 1;
sign = 0;
fmt++;
continue;
case '.':
fmt++;
while (*fmt >= '0' && *fmt <= '9') {
frac_width = frac_width * 10 + *fmt++ - '0';
}
break;
case '*':
slen = va_arg(args, size_t);
fmt++;
continue;
default:
break;
}
break;
}
switch (*fmt) {
case 'V':
v = va_arg(args, ngx_str_t *);
len = ngx_min(((size_t) (last - buf)), v->len);
buf = ngx_cpymem(buf, v->data, len);
fmt++;
continue;
case 'v':
vv = va_arg(args, ngx_variable_value_t *);
len = ngx_min(((size_t) (last - buf)), vv->len);
buf = ngx_cpymem(buf, vv->data, len);
fmt++;
continue;
case 's':
p = va_arg(args, u_char *);
if (slen == (size_t) -1) {
while (*p && buf < last) {
*buf++ = *p++;
}
} else {
len = ngx_min(((size_t) (last - buf)), slen);
buf = ngx_cpymem(buf, p, len);
}
fmt++;
continue;
case 'O':
i64 = (int64_t) va_arg(args, off_t);
sign = 1;
break;
case 'P':
i64 = (int64_t) va_arg(args, ngx_pid_t);
sign = 1;
break;
case 'T':
i64 = (int64_t) va_arg(args, time_t);
sign = 1;
break;
case 'M':
ms = (ngx_msec_t) va_arg(args, ngx_msec_t);
if ((ngx_msec_int_t) ms == -1) {
sign = 1;
i64 = -1;
} else {
sign = 0;
ui64 = (uint64_t) ms;
}
break;
case 'z':
if (sign) {
i64 = (int64_t) va_arg(args, ssize_t);
} else {
ui64 = (uint64_t) va_arg(args, size_t);
}
break;
case 'i':
if (sign) {
i64 = (int64_t) va_arg(args, ngx_int_t);
} else {
ui64 = (uint64_t) va_arg(args, ngx_uint_t);
}
if (max_width) {
width = NGX_INT_T_LEN;
}
break;
case 'd':
if (sign) {
i64 = (int64_t) va_arg(args, int);
} else {
ui64 = (uint64_t) va_arg(args, u_int);
}
break;
case 'l':
if (sign) {
i64 = (int64_t) va_arg(args, long);
} else {
ui64 = (uint64_t) va_arg(args, u_long);
}
break;
case 'D':
if (sign) {
i64 = (int64_t) va_arg(args, int32_t);
} else {
ui64 = (uint64_t) va_arg(args, uint32_t);
}
break;
case 'L':
if (sign) {
i64 = va_arg(args, int64_t);
} else {
ui64 = va_arg(args, uint64_t);
}
break;
case 'A':
if (sign) {
i64 = (int64_t) va_arg(args, ngx_atomic_int_t);
} else {
ui64 = (uint64_t) va_arg(args, ngx_atomic_uint_t);
}
if (max_width) {
width = NGX_ATOMIC_T_LEN;
}
break;
case 'f':
f = va_arg(args, double);
if (f < 0) {
*buf++ = '-';
f = -f;
}
ui64 = (int64_t) f;
buf = ngx_sprintf_num(buf, last, ui64, zero, 0, width);
if (frac_width) {
if (buf < last) {
*buf++ = '.';
}
scale = 1.0;
for (n = frac_width; n; n--) {
scale *= 10.0;
}
/*
* (int64_t) cast is required for msvc6:
* it can not convert uint64_t to double
*/
ui64 = (uint64_t) ((f - (int64_t) ui64) * scale + 0.5);
buf = ngx_sprintf_num(buf, last, ui64, '0', 0, frac_width);
}
fmt++;
continue;
#if !(NGX_WIN32)
case 'r':
i64 = (int64_t) va_arg(args, rlim_t);
sign = 1;
break;
#endif
case 'p':
ui64 = (uintptr_t) va_arg(args, void *);
hex = 2;
sign = 0;
zero = '0';
width = NGX_PTR_SIZE * 2;
break;
case 'c':
d = va_arg(args, int);
*buf++ = (u_char) (d & 0xff);
fmt++;
continue;
case 'Z':
*buf++ = '\0';
fmt++;
continue;
case 'N':
#if (NGX_WIN32)
*buf++ = CR;
#endif
*buf++ = LF;
fmt++;
continue;
case '%':
*buf++ = '%';
fmt++;
continue;
default:
*buf++ = *fmt++;
continue;
}
if (sign) {
if (i64 < 0) {
*buf++ = '-';
ui64 = (uint64_t) -i64;
} else {
ui64 = (uint64_t) i64;
}
}
buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
fmt++;
} else {
*buf++ = *fmt++;
}
}
return buf;
}
IMO writing similar stuff only causes waste of memory,your idea?

Whether it's "worth it" is subjective - there are lots of things to consider:
What advantages does it give over sprintf? Do you need them?
Are you willing to live with whatever shortcomings may be there?
Do you understand the code well enough to be able to fix it if problems are found?
Is it as fast as sprintf? If not, do you care?
You may also want to consider writing a cover function that adds whatever extra functionality you need but then just calls sprintf, rather than re-implement the entire function.

I think this kind of thing is not only wasteful but actively harmful. The name includes printf, which would lead a reasonable person first seeing code that's using it to assume its format strings are printf-compatible. But in fact they're only a very poor approximation of printf semantics. This could lead to extremely serious, security-critical bugs, which might go undetected if the code only appears in a non-common-usage case. The C standard has a perfectly safe and usable snprintf function which should always be used when this functionality is needed. And shame on whoever designed this junk in the name of "security"...

Related

Custom print function has undesired behaviour

I'm making my own x86 OS using the i386-elf cross-compiler and linker and nasm to compile asm files. The OS itself runs with qemu. That being said, I made a custom print function but ran into a problem. Every time I access memory (either through the [] operator or by dereferencing a pointer) and call my print function afterwards, it leaves 8 blank spaces and then prints normally.
Print code:
void printv(char *str, ...)
{
unsigned int tmp_cursor = get_cursor_position();
cursor_position.x = (unsigned short)(tmp_cursor >> 16);
cursor_position.y = (unsigned short)tmp_cursor;
char buffer[12];
va_list list_ptr;
va_start(list_ptr, str);
unsigned int i = 0;
for (char *ptr = str; *ptr != '\0'; ptr++)
{
switch (*ptr)
{
case '%':
cursor_position.y += (cursor_position.x + i) / 80;
cursor_position.x = (cursor_position.x + i) % 80;
update_cursor(cursor_position.x, cursor_position.y);
i = 0;
switch (*(ptr + 1))
{
case 'c':
buffer[0] = (char)va_arg(list_ptr, int);
buffer[1] = '\0';
printv(buffer);
ptr++;
break;
case 's':
printv(va_arg(list_ptr, char *));
ptr++;
break;
case 'i':
case 'd':
int_to_str(va_arg(list_ptr, int), buffer, 10);
printv(buffer);
ptr++;
break;
default:
*(char*)(0xb8000 + (cursor_position.x + i + cursor_position.y * 80) * 2) = *ptr;
i++;
break;
}
break;
case '\n':
i = 0;
cursor_position.x = 0;
cursor_position.y++;
break;
case '\t':
cursor_position.y += (cursor_position.x + i) / 80;
cursor_position.x = (cursor_position.x + i) % 80;
update_cursor(cursor_position.x, cursor_position.y);
i = 0;
cursor_position.x += TAB_SPACE - cursor_position.x % TAB_SPACE - 1;
break;
default:
*(char *)(0xb8000 +(cursor_position.x + i + cursor_position.y * 80) * 2) = *ptr;
i++;
break;
}
}
va_end(list_ptr);
memset(buffer, '\0', 12);
cursor_position.y += (cursor_position.x + i) / 80;
cursor_position.x = (cursor_position.x + i) % 80;
update_cursor(cursor_position.x, cursor_position.y);
}
Call example:
printv("Starting PonchOS!\n");
char str[12];
for (int i = 0; i < 11; i++)
{
str[i] = 'a' + i;
}
str[11] = '\0';
printv("Testtesttesttesttest");
Output:
As you can see, it prints fine before any memory access, but after that, it leaves those white spaces. Any ideas as to why this happens?
Edit:
Implementing #chqrlie 's changes, some issues have been fixed, although spacing problems persist.
Code:
printv("Starting PonchOS!\n");
printv("%c\n", 'C');
printv("%i", 128);
printv("%s", "string");
Output:
The problem comes from your not updating the cursor variables consistently when calling printv recursively. Furthermore you would get undefined behavior for this call: printv("%s", "%s").
You should split the function into a high level one that handles the formatting and a low level one that draws a string to the screen.
Here is a modified version:
void putstr(const char *str, size_t n) {
if (n > 0) {
unsigned int tmp_cursor = get_cursor_position();
int x = (unsigned short)(tmp_cursor >> 16);
int y = (unsigned short)tmp_cursor;
size_t i;
for (i = 0; i < n; i++) {
switch (str[i]) {
case '\n':
y += x / 80 + 1;
x = 0;
break;
case '\r':
y += x / 80;
x = 0;
break;
case '\t':
x = (x + TAB_SPACE) / TAB_SPACE * TAB_SPACE;
y += x / 80;
x %= 80;
break;
default:
*(char *)(0xb8000 + (y * 80 + x) * 2) = str[i];
x++;
break;
}
}
update_cursor(x, y);
}
}
void printv(const char *str, ...) {
char buffer[32];
char *p;
const char *ptr;
va_list list_ptr;
va_start(list_ptr, str);
for (ptr = str; *ptr != '\0'; ptr++) {
if (*ptr == '%' && ptr[1] != '\0') {
putstr(str, ptr - str);
str = ptr;
ptr++;
switch (*ptr) {
case 'c':
buffer[0] = (char)va_arg(list_ptr, int);
putstr(buffer, 1);
str += 2; // skip the format
break;
case 's':
p = va_arg(list_ptr, char *);
putstr(p, strlen(p));
str += 2; // skip the format
break;
case 'i':
case 'd':
int_to_str(va_arg(list_ptr, int), buffer, 10);
putstr(buffer, strlen(buffer));
str += 2; // skip the format
break;
case '%':
str += 1; // skip the initial %
break;
}
}
}
putstr(str, ptr - str);
va_end(list_ptr);
}

How can I put sin cos function into this RPN calculator

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#define MAXVAL 100
#define MAXOP 100
#define NUMBER '0'
#define BUFSIZE 100
#define sinn 'sin'
int sp = 0;
double val[MAXVAL];
char buf[BUFSIZE];
int bufp = 0;
double aatof(char []);
void push(double);
double pop(void);
int ggetop(char []);
int ggetch(void);
void unggetch(int);
int main()
{
int type;
double op2;
int op3;
char s[MAXOP];
while ((type = ggetop(s)) != EOF)
{
switch (type)
{
case NUMBER:
push(aatof(s));
break;
case '+':
push(pop() + pop());
break;
case '*':
push(pop() * pop());
break;
case '-': // negative number
if (isdigit(ggetop(s))){
push(0-aatof(s));
break;}
case '--': // minus
op2 = pop();
push(pop() - op2);
break;
case '/':
op2 = pop();
if (op2 != 0.0)
push(pop() / op2);
else
printf("error: zero divisor\n");
break;
case '%':
op3 = (int)pop();
if (op3 != 0)
push((int)pop() % op3);
else
printf("error: zero divisor\n");
break;
case 'sin':
push(sin(pop()));
break;
case '\n':
printf("\t%.8g\n", pop());
break;
default:
printf("error: unknown command %s\n", s);
break;
}
}
return 0;
}
int ggetop(char s[])
{
int i, c;
while ((s[0] = c = ggetch()) == ' ' || c == '\t')
;
s[1] = '\0';
if (!isdigit(c) && c != '.')
i = 0;
if (isdigit(c))
while (isdigit(s[++i] = c = ggetch()))
;
if (c == '.')
while (isdigit(s[++i] = c = ggetch()))
;
s[i] = '\0';
if (c != EOF)
unggetch(c);
return NUMBER;
}
int ggetch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
void unggetch(int c)
{
if (bufp >= BUFSIZE)
printf("unggetch: too many characters\n");
else
buf[bufp++] = c;
}
double aatof(char s[])
{
double val, power;
int i, sign;
for (i = 0; isspace(s[i]); i++)
;
sign = (s[i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-')
i++;
for (val = 0.0; isdigit(s[i]); i++)
val = 10.0 * val + (s[i] - '0');
if (s[i] == '.')
i++;
for (power = 1.0; isdigit(s[i]); i++)
{
val = 10.0 * val + (s[i] - '0');
power *= 10.0;
}
return sign * val / power;
}
void push(double f)
{
if (sp < MAXVAL)
val[sp++] = f;
else
printf("error: stack full, can't push %g\n", f);
}
double pop(void)
{
if (sp > 0)
return val[--sp];
else
{
printf("error: stack empty\n");
return 0.0;
}
}
I am not sure how can I put the sin cos pow function into different cases. I've tried to use isalpha() in ggetop(char s[]) and turn letters into integer but it doesn't seem to work. what I want to do is process the letter and give back the ASCII number, and add them into type. I wonder how to process the letters altogether at once and not individually.
if(isalpha(c)){
s[++i] = c = ggetch();
return c;}
are there other ways to add sin cos and pow functions into the programs?

copying value of void pointer C

I have a problem with function which should print any type of integer on my KS0108 lcd. Here the problem is: when i want to copy void* numb value to void* temp in first swtich - nothing happens. in second switch when use %I get 0. (value of void* numb is about 48 and type _uint8_t)
Code:
void glcd_WriteInt(void* numb, type type)
{
void* temp = 0;
int8_t buff[10];
int8_t size = 0;
int8_t flag = 1;
int8_t sign = 1;
switch(type)
{
case _int8_t: memcpy(temp, numb, sizeof(int8_t)); break;
case _int16_t: memcpy(temp, numb, sizeof(int16_t)); break;
case _int32_t: memcpy(temp, numb, sizeof(int32_t)); break;
case _int64_t: /*memcpy(temp, numb, sizeof(int64_t)); break;*/return; //if want 64 - uncomment
case _uint8_t: *(uint8_t*) temp = *(uint8_t*) numb/*memcpy(temp, numb, sizeof(uint8_t))*/; break;
case _uint16_t: memcpy(temp, numb, sizeof(uint16_t)); break;
case _uint32_t: memcpy(temp, numb, sizeof(uint32_t)); break;
case _uint64_t: /*memcpy(temp, numb, sizeof(uint64_t)); break;*/return; //if want 64 - uncomment
}
while(flag)
{
switch(type)
{
case _int8_t:
buff[size] = *(int8_t*) temp % 100;
flag = (*(int8_t*) temp /= 100) != 0;
break;
case _int16_t:
buff[size] = *(int16_t*) temp % 100;
flag = (*(int16_t*) temp /= 100) != 0;
break;
case _int32_t:
buff[size] = *(int32_t*) temp % 100;
flag = (*(int32_t*) temp /= 100) != 0;
break;
case _int64_t:
/*buff[size] = *(int64_t*) temp % 100; //if want 64 - uncomment
flag = (*(int64_t*) temp /= 100) != 0;*/
break;
case _uint8_t:
buff[size] = *(uint8_t*) temp % 100;
flag = (*(uint8_t*) temp /= 100) != 0;
break;
case _uint16_t:
buff[size] = *(uint16_t*) temp % 100;
flag = (*(uint16_t*) temp /= 100) != 0;
break;
case _uint32_t:
buff[size] = *(uint32_t*) temp % 100;
flag = (*(uint32_t*) temp /= 100) != 0;
break;
case _uint64_t:
/*buff[size] = *(uint64_t*) temp % 100; //if want 64 - uncomment
flag = (*(uint64_t*) temp /= 100) != 0;*/
break;
}
if(buff[size] < 0){
buff[size] = -buff[size];
sign = -1;
}
size++;
}
if(sign == -1) glcd_Character((char) 45);
glcd_WIntTab(buff, size);
}
I tried versions with memcpy, and like here: casts. It is suprising that this function was OK a few days ago.
Your main problem is that you have
void *temp = 0;
declared, and you try to write to a NULL pointer since you initialize it with void *temp = 0.
Even if you don't initialize it, it wont work because it wouldn't be a valid pointer, so either you allocate memory with malloc and assign it to the void *temp pointer, or use a union this way
union Type {
int8_t _int8_t;
int16_t _int16_t;
int32_t _int32_t;
int64_t _int64_t;
uint8_t _uint8_t;
uint16_t _uint16_t;
uint32_t _uint32_t;
uint64_t _uint64_t;
};
void glcd_WriteInt(void* numb, enum type type)
{
union Type temp;
int8_t buff[10];
int8_t size = 0;
int8_t flag = 1;
int8_t sign = 1;
switch (type)
{
case _int8_t: memcpy(&temp, numb, sizeof(int8_t)); break;
case _int16_t: memcpy(&temp, numb, sizeof(int16_t)); break;
case _int32_t: memcpy(&temp, numb, sizeof(int32_t)); break;
case _int64_t: /*memcpy(&temp, numb, sizeof(int64_t)); break;*/return; //if want 64 - uncomment
case _uint8_t: memcpy(&temp, numb, sizeof(uint8_t))/*memcpy(temp, numb, sizeof(uint8_t))*/; break;
case _uint16_t: memcpy(&temp, numb, sizeof(uint16_t)); break;
case _uint32_t: memcpy(&temp, numb, sizeof(uint32_t)); break;
case _uint64_t: /*memcpy(&temp, numb, sizeof(uint64_t)); break;*/return; //if want 64 - uncomment
}
while (flag)
{
switch (type)
{
case _int8_t:
buff[size] = temp._int8_t % 100;
flag = (temp._int8_t /= 100) != 0;
break;
case _int16_t:
buff[size] = temp._int16_t % 100;
flag = (temp._int16_t /= 100) != 0;
break;
case _int32_t:
buff[size] = temp._int32_t % 100;
flag = (temp._int32_t /= 100) != 0;
break;
case _int64_t:
buff[size] = temp._int64_t % 100;
flag = (temp._int64_t /= 100) != 0;
break;
case _uint8_t:
buff[size] = temp._uint8_t % 100;
flag = (temp._uint8_t /= 100) != 0;
break;
case _uint16_t:
buff[size] = temp._uint16_t % 100;
flag = (temp._uint16_t /= 100) != 0;
break;
case _uint32_t:
buff[size] = temp._uint32_t % 100;
flag = (temp._uint32_t /= 100) != 0;
break;
case _uint64_t:
buff[size] = temp._uint64_t % 100;
flag = (temp._uint64_t /= 100) != 0;
break;
}
if (buff[size] < 0)
{
buff[size] = -buff[size];
sign = -1;
}
size++;
}
if (sign == -1)
glcd_Character((char) 45);
glcd_WIntTab(buff, size);
}
I believe the union is a better solution.

Minimal implementation of sprintf or printf

I'm working on an embedded DSP where speed is crucial, and memory is very short.
At the moment, sprintf uses the most resources of any function in my code. I only use it to format some simple text: %d, %e, %f, %s, nothing with precision or exotic manipulations.
How can I implement a basic sprintf or printf function that would be more suitable for my usage?
This one assumes the existence of an itoa to convert an int to character representation, and an fputs to write out a string to wherever you want it to go.
The floating point output is non-conforming in at least one respect: it makes no attempt at rounding correctly, as the standard requires, so if you have have (for example) a value of 1.234 that is internally stored as 1.2399999774, it'll be printed out as 1.2399 instead of 1.2340. This saves quite a bit of work, and remains sufficient for most typical purposes.
This also supports %c and %x in addition to the conversions you asked about, but they're pretty trivial to remove if you want to get rid of them (and doing so will obviously save a little memory).
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>
static void ftoa_fixed(char *buffer, double value);
static void ftoa_sci(char *buffer, double value);
int my_vfprintf(FILE *file, char const *fmt, va_list arg) {
int int_temp;
char char_temp;
char *string_temp;
double double_temp;
char ch;
int length = 0;
char buffer[512];
while ( ch = *fmt++) {
if ( '%' == ch ) {
switch (ch = *fmt++) {
/* %% - print out a single % */
case '%':
fputc('%', file);
length++;
break;
/* %c: print out a character */
case 'c':
char_temp = va_arg(arg, int);
fputc(char_temp, file);
length++;
break;
/* %s: print out a string */
case 's':
string_temp = va_arg(arg, char *);
fputs(string_temp, file);
length += strlen(string_temp);
break;
/* %d: print out an int */
case 'd':
int_temp = va_arg(arg, int);
itoa(int_temp, buffer, 10);
fputs(buffer, file);
length += strlen(buffer);
break;
/* %x: print out an int in hex */
case 'x':
int_temp = va_arg(arg, int);
itoa(int_temp, buffer, 16);
fputs(buffer, file);
length += strlen(buffer);
break;
case 'f':
double_temp = va_arg(arg, double);
ftoa_fixed(buffer, double_temp);
fputs(buffer, file);
length += strlen(buffer);
break;
case 'e':
double_temp = va_arg(arg, double);
ftoa_sci(buffer, double_temp);
fputs(buffer, file);
length += strlen(buffer);
break;
}
}
else {
putc(ch, file);
length++;
}
}
return length;
}
int normalize(double *val) {
int exponent = 0;
double value = *val;
while (value >= 1.0) {
value /= 10.0;
++exponent;
}
while (value < 0.1) {
value *= 10.0;
--exponent;
}
*val = value;
return exponent;
}
static void ftoa_fixed(char *buffer, double value) {
/* carry out a fixed conversion of a double value to a string, with a precision of 5 decimal digits.
* Values with absolute values less than 0.000001 are rounded to 0.0
* Note: this blindly assumes that the buffer will be large enough to hold the largest possible result.
* The largest value we expect is an IEEE 754 double precision real, with maximum magnitude of approximately
* e+308. The C standard requires an implementation to allow a single conversion to produce up to 512
* characters, so that's what we really expect as the buffer size.
*/
int exponent = 0;
int places = 0;
static const int width = 4;
if (value == 0.0) {
buffer[0] = '0';
buffer[1] = '\0';
return;
}
if (value < 0.0) {
*buffer++ = '-';
value = -value;
}
exponent = normalize(&value);
while (exponent > 0) {
int digit = value * 10;
*buffer++ = digit + '0';
value = value * 10 - digit;
++places;
--exponent;
}
if (places == 0)
*buffer++ = '0';
*buffer++ = '.';
while (exponent < 0 && places < width) {
*buffer++ = '0';
--exponent;
++places;
}
while (places < width) {
int digit = value * 10.0;
*buffer++ = digit + '0';
value = value * 10.0 - digit;
++places;
}
*buffer = '\0';
}
void ftoa_sci(char *buffer, double value) {
int exponent = 0;
int places = 0;
static const int width = 4;
if (value == 0.0) {
buffer[0] = '0';
buffer[1] = '\0';
return;
}
if (value < 0.0) {
*buffer++ = '-';
value = -value;
}
exponent = normalize(&value);
int digit = value * 10.0;
*buffer++ = digit + '0';
value = value * 10.0 - digit;
--exponent;
*buffer++ = '.';
for (int i = 0; i < width; i++) {
int digit = value * 10.0;
*buffer++ = digit + '0';
value = value * 10.0 - digit;
}
*buffer++ = 'e';
itoa(exponent, buffer, 10);
}
int my_printf(char const *fmt, ...) {
va_list arg;
int length;
va_start(arg, fmt);
length = my_vfprintf(stdout, fmt, arg);
va_end(arg);
return length;
}
int my_fprintf(FILE *file, char const *fmt, ...) {
va_list arg;
int length;
va_start(arg, fmt);
length = my_vfprintf(file, fmt, arg);
va_end(arg);
return length;
}
#ifdef TEST
int main() {
float floats[] = { 0.0, 1.234e-10, 1.234e+10, -1.234e-10, -1.234e-10 };
my_printf("%s, %d, %x\n", "Some string", 1, 0x1234);
for (int i = 0; i < sizeof(floats) / sizeof(floats[0]); i++)
my_printf("%f, %e\n", floats[i], floats[i]);
return 0;
}
#endif
I wrote nanoprintf in an attempt to find a balance between tiny binary size and having good feature coverage. As of today the "bare-bones" configuration is < 800 bytes of binary code, and the "maximal" configuration including float parsing is < 2500 bytes. 100% C99 code, no external dependencies, one header file.
https://github.com/charlesnicholson/nanoprintf
I haven't seen a smaller vsnprintf implementation than this that has a comparable feature set. I also released the software in the public domain and with the Zero-clause BSD license so it's fully unencumbered.
Here's an example that uses the vsnprintf functionality:
your_project_nanoprintf.c
#define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0
// Compile nanoprintf in this translation unit.
#define NANOPRINTF_IMPLEMENTATION
#include "nanoprintf.h"
your_log.h
void your_log(char const *s);
void your_log_v(char const *fmt, ...);
your_log.c
#include "your_log.h"
#include "nanoprintf.h"
#include <stdarg.h>
void your_log_v(char const *s) {
// Do whatever you want with the fully formatted string s.
}
void your_log(char const *fmt, ...) {
char buf[128];
va_arg args;
va_start(args, fmt);
npf_vsnprintf(buf, sizeof(buf), fmt, args); // Use nanoprintf for formatting.
va_end(args);
your_log_write(buf);
}
Nanoprintf also provides an snprintf-alike and a custom version that takes a user-provided putc callback for things like UART writes.
I add here my own implementation of (v)sprintf, but it does not provide float support (it is why I am here...).
However, it implements the specifiers c, s, d, u, x and the non standard ones b and m (binary and memory hexdump); and also the flags 0, 1-9, *, +.
#include <stdarg.h>
#include <stdint.h>
#define min(a,b) __extension__\
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; })
enum flag_itoa {
FILL_ZERO = 1,
PUT_PLUS = 2,
PUT_MINUS = 4,
BASE_2 = 8,
BASE_10 = 16,
};
static char * sitoa(char * buf, unsigned int num, int width, enum flag_itoa flags)
{
unsigned int base;
if (flags & BASE_2)
base = 2;
else if (flags & BASE_10)
base = 10;
else
base = 16;
char tmp[32];
char *p = tmp;
do {
int rem = num % base;
*p++ = (rem <= 9) ? (rem + '0') : (rem + 'a' - 0xA);
} while ((num /= base));
width -= p - tmp;
char fill = (flags & FILL_ZERO)? '0' : ' ';
while (0 <= --width) {
*(buf++) = fill;
}
if (flags & PUT_MINUS)
*(buf++) = '-';
else if (flags & PUT_PLUS)
*(buf++) = '+';
do
*(buf++) = *(--p);
while (tmp < p);
return buf;
}
int my_vsprintf(char * buf, const char * fmt, va_list va)
{
char c;
const char *save = buf;
while ((c = *fmt++)) {
int width = 0;
enum flag_itoa flags = 0;
if (c != '%') {
*(buf++) = c;
continue;
}
redo_spec:
c = *fmt++;
switch (c) {
case '%':
*(buf++) = c;
break;
case 'c':;
*(buf++) = va_arg(va, int);
break;
case 'd':;
int num = va_arg(va, int);
if (num < 0) {
num = -num;
flags |= PUT_MINUS;
}
buf = sitoa(buf, num, width, flags | BASE_10);
break;
case 'u':
buf = sitoa(buf, va_arg(va, unsigned int), width, flags | BASE_10);
break;
case 'x':
buf = sitoa(buf, va_arg(va, unsigned int), width, flags);
break;
case 'b':
buf = sitoa(buf, va_arg(va, unsigned int), width, flags | BASE_2);
break;
case 's':;
const char *p = va_arg(va, const char *);
if (p) {
while (*p)
*(buf++) = *(p++);
}
break;
case 'm':;
const uint8_t *m = va_arg(va, const uint8_t *);
width = min(width, 64); // buffer limited to 256!
if (m)
for (;;) {
buf = sitoa(buf, *(m++), 2, FILL_ZERO);
if (--width <= 0)
break;
*(buf++) = ':';
}
break;
case '0':
if (!width)
flags |= FILL_ZERO;
// fall through
case '1'...'9':
width = width * 10 + c - '0';
goto redo_spec;
case '*':
width = va_arg(va, unsigned int);
goto redo_spec;
case '+':
flags |= PUT_PLUS;
goto redo_spec;
case '\0':
default:
*(buf++) = '?';
}
width = 0;
}
*buf = '\0';
return buf - save;
}
int my_sprintf(char * buf, const char * fmt, ...)
{
va_list va;
va_start(va,fmt);
int ret = my_vsprintf(buf, fmt, va);
va_end(va);
return ret;
}
#if TEST
int main(int argc, char *argv[])
{
char b[256], *p = b;
my_sprintf(b, "%x %d %b\n", 123, 123, 123);
while (*p)
putchar(*p++);
}
#endif
tl;dr : Considering a smaller, but more complete, sprintf() implementation
https://github.com/eyalroz/printf
The standard library's sprintf() implementation you may be using is probably quite resource-taxing. But it's possible that you could avail yourself of a stand-alone sprintf() implementation, you would get more complete functionality without paying with so much memory use.
Now, why would you choose that if you've told us you only need some basic functionality? Because the nature of (s)printf() use is that we tend to use more aspects of it as we go along. You notice you want to print larger numbers, or differences in far decimal digits; you want to print a bunch of values and then decide you want them aligned. Or somebody else wants to use the printing capability you added to print something you haven't thought of. So, instead of having to switch implementations, you use an implementation where compile-time options configure which features get compiled and which get left out.

Implementation of printf for float in GNU C , semihosting

I need to use gnu c printf function to send floating point number to semihosting console.
The current implementation printf(vsnprintf) is
signed int vsnprintf(char *pStr, size_t length, const char *pFormat, va_list ap)
{
char fill;
unsigned char width;
signed int num = 0;
signed int size = 0;
/* Clear the string */
if (pStr) {
*pStr = 0;
}
/* Phase string */
while (*pFormat != 0 && size < length) {
/* Normal character */
if (*pFormat != '%') {
*pStr++ = *pFormat++;
size++;
}
/* Escaped '%' */
else if (*(pFormat+1) == '%') {
*pStr++ = '%';
pFormat += 2;
size++;
}
/* Token delimiter */
else {
fill = ' ';
width = 0;
pFormat++;
/* Parse filler */
if (*pFormat == '0') {
fill = '0';
pFormat++;
}
/* Parse width */
while ((*pFormat >= '0') && (*pFormat <= '9')) {
width = (width*10) + *pFormat-'0';
pFormat++;
}
/* Check if there is enough space */
if (size + width > length) {
width = length - size;
}
/* Parse type */
switch (*pFormat) {
case 'd':
case 'i': num = PutSignedInt(pStr, fill, width, va_arg(ap, signed int)); break;
case 'u': num = PutUnsignedInt(pStr, fill, width, va_arg(ap, unsigned int)); break;
case 'x': num = PutHexa(pStr, fill, width, 0, va_arg(ap, unsigned int)); break;
case 'X': num = PutHexa(pStr, fill, width, 1, va_arg(ap, unsigned int)); break;
case 's': num = PutString(pStr, va_arg(ap, char *)); break;
case 'c': num = PutChar(pStr, va_arg(ap, unsigned int)); break;
default:
return EOF;
}
pFormat++;
pStr += num;
size += num;
}
}
/* NULL-terminated (final \0 is not counted) */
if (size < length) {
*pStr = 0;
}
else {
*(--pStr) = 0;
size--;
}
return size;
}
Any help to implement 'f' format specifier is greatly appreciated
It seems you are using a custom printf implementation as opposed to using one from libc your toolchain. Provided you have implemented syscalls, you should be able to simply switch to the standard printf implementation of the toolchain by simply not compiling in your stdio implementation.
Another way could be to make a PutFloat function that simply multiplies the input by a power of 10 and then separately prints the above and below decimal parts of the number using existing integer prints. For example:
x = (signed int)floatIn*10000;
PutSignedInt(x/10000);
PutChar('.');
ax = abs(x);
ay = abs(y);
ax = ax - ay*10000;
PutSignedInt(ax);
If you get the idea, you should be able to fill in the details yourself.

Resources