Вы должны проанализировать строку формата в своей функции и вызвать printf
с соответствующим типом значения. Чтобы прочитать значение, вы можете привести указатель void
к соответствующему типу, определенному спецификатором преобразования.
Вот быстрый пример:
#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;
}
}
Примечания:
форматы с плавающей запятой ведут себя как scanf()
: используйте %f
, если data
указывает на float
и %lf
, если он указывает на double
. l
будет игнорироваться printf
, поскольку значения float
преобразуются в double
при передаче в функции vararg.
эта функция ожидает указатель на char
для формат %c
, хотя printf
ожидает int
, который будет преобразован в unsigned char
.
эта функция ожидает указатель на wchar_t
для формата %lc
хотя printf
ожидает wint_t
.
спецификаторы преобразования %zd
и %tu
, разрешенные стандартом C, но соответствующие типы не определены стандартом. Передача типа с другой подписью не является строго правильной для отрицательных значений, но вряд ли создаст проблему.