You must parse the format string in your function and call printf
with the appropriate value type. To read the value, you can cast the void
pointer to the appropriate type as determined by the conversion specifier.
Here is a quick example:
#include <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#define printf printf__
int printf(const char *, ...);
int print_data(const char *format, void *data) {
const char *p = format;
enum {
FMT_none = 0,
FMT_c = 1,
FMT_i = 2,
FMT_u = 3,
FMT_f = 4,
FMT_pc = 5,
FMT_pv = 6,
PREF_l = (1 << 3),
PREF_ll = (1 << 4),
PREF_h = (1 << 5),
PREF_hh = (1 << 6),
PREF_j = (1 << 7),
PREF_z = (1 << 8),
PREF_t = (1 << 9),
PREF_L = (1 << 10),
};
int fmt = FMT_none;
for (;;) {
int cur_fmt = FMT_none;
int prefix = 0;
p = strchr(p, '%');
if (!p)
break;
p++; // skip the '%'
// skip the flag characters, width and precision
// note that invalid combinations will not be detected
// such as %..d or %.+d
p += strspn(p, " -#+0123456789.");
// parse the length modifier if present
switch (*p) {
case 'l':
p++;
prefix = PREF_l;
if (*p == 'l') {
p++;
prefix = PREF_ll;
}
break;
case 'h':
p++;
prefix = PREF_h;
if (*p == 'h') {
p++;
prefix = PREF_hh;
}
break;
case 'j':
p++;
prefix = PREF_j;
break;
case 'z':
p++;
prefix = PREF_z;
break;
case 't':
p++;
prefix = PREF_t;
break;
case 'L':
p++;
prefix = PREF_L;
break;
}
switch (*p++) {
case '%':
if (p[-2] != '%')
return -1;
continue;
case 'c':
cur_fmt = FMT_c;
break;
case 'd':
case 'i':
cur_fmt = FMT_i;
break;
case 'o':
case 'u':
case 'x': case 'X':
cur_fmt = FMT_u;
break;
case 'a': case 'A':
case 'e': case 'E':
case 'f': case 'F':
case 'g': case 'G':
cur_fmt = FMT_f;
break;
case 's':
cur_fmt = FMT_pc;
break;
case 'p':
cur_fmt = FMT_pv;
break;
default:
return -1;
}
if (fmt != FMT_none)
return -1; // more than one format
fmt = cur_fmt | prefix;
}
switch (fmt) {
case FMT_none:
return printf(format);
case FMT_c:
return printf(format, *(char *)data);
case FMT_c | PREF_l:
// the (wint_t) cast is redundant, omitted
return printf(format, *(wchar_t *)data);
case FMT_i:
return printf(format, *(int *)data);
case FMT_i | PREF_l:
return printf(format, *(long *)data);
case FMT_i | PREF_ll:
return printf(format, *(long long *)data);
case FMT_i | PREF_h:
return printf(format, *(short *)data);
case FMT_i | PREF_hh:
return printf(format, *(signed char *)data);
case FMT_i | PREF_j:
return printf(format, *(intmax_t *)data);
case FMT_i | PREF_z:
case FMT_u | PREF_z:
return printf(format, *(size_t *)data);
case FMT_i | PREF_t:
case FMT_u | PREF_t:
return printf(format, *(ptrdiff_t *)data);
case FMT_u:
return printf(format, *(unsigned *)data);
case FMT_u | PREF_l:
return printf(format, *(unsigned long *)data);
case FMT_u | PREF_ll:
return printf(format, *(unsigned long long *)data);
case FMT_u | PREF_h:
return printf(format, *(unsigned short *)data);
case FMT_u | PREF_hh:
return printf(format, *(unsigned char *)data);
case FMT_u | PREF_j:
return printf(format, *(uintmax_t *)data);
case FMT_f:
// the cast (double) is redundant, but useful to prevent warnings
return printf(format, (double)*(float *)data);
case FMT_f | PREF_l:
return printf(format, *(double *)data);
case FMT_f | PREF_L:
return printf(format, *(long double *)data);
case FMT_pc:
return printf(format, *(char **)data);
case FMT_pc | PREF_l:
return printf(format, *(wchar_t **)data);
case FMT_pv:
return printf(format, *(void **)data);
default:
return -1;
}
}
Notes:
the floating point formats behave like scanf()
: use %f
if data
points to a float
and %lf
if it points to a double
. The l
will be ignored by printf
as float
values are converted to double
when passed to vararg functions.
this function expects a pointer to char
for a %c
format although printf
expects an int
that will be converted to unsigned char
.
this function expects a pointer to wchar_t
for a %lc
format although printf
expects a wint_t
.
conversion specifiers %zd
and %tu
allowed by the C Standard but the corresponding types are not defined by the standard. Passing the type with the other signedness is not strictly correct for negative values but unlikely to pose a problem.