我将简化我的情况,以便专注于实际问题。
假设我正在为printf
编写一个名为print_data
的掩护函数。用户调用print_data
并传入单一格式字符串,例如"%.1f"
以及表示数据的void *
:
void print_data(const char *format, void *data);
我的工作是接受这些论点并以某种方式将它们传递给printf
。
我的问题是 printf 需要一个值而不是一个指针(字符串除外)。我无法确定用户传入的数据类型,只能自己手动读取格式字符串并相应地转换数据(例如,如果传递了"f",则转换为float
)。
一个"神奇"的解决方案是以某种方式取消引用void*
的能力,但这当然是不可能的。
不幸的是,我无法重组设计,因为问题不是这么简单,需要我收到void*
和格式字符串。
我的问题几乎与给定指针和格式字符串的 printf 相同。浮点问题,除了看起来解决方案没有解决。
关于我如何实现这一目标的任何想法?
您必须分析函数中的格式字符串并使用适当的值类型调用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()
:如果data
指向float
则使用%f
,如果指向double
,则使用%lf
。l
将被printf
忽略,因为float
值在传递给 vararg 函数时将转换为double
。此函数需要一个指向
%c
格式的char
的指针,尽管printf
需要一个将转换为unsigned char
的int
。此函数需要指向
%lc
格式的wchar_t
的指针,尽管printf
需要wint_t
。转换说明符
%zd
C 标准允许和%tu
,但标准未定义相应的类型。对于负值,传递带有其他符号的类型并不严格正确,但不太可能造成问题。
关于我如何实现这一目标的任何想法?
解析格式 - 鉴于该格式不仅具有说明符"acdefginopsuxAEFGX%"
而且修饰符"hlhhlljztL"
,因此有很多可能性。 这很快就会产生数十种甚至 100+ 种有效的原始组合。
存在简化:说明符"aefgAEFG"
都是double
或更宽的。"uoxX"
unsigned
或更宽。 我认为有效说明符/修饰符组合的数量可以归结为 (2(fp) +8(i) +8(u) +2(c) +2(s) +1(1))。
这里可能需要禁止说明符"%n"
- 没有多大意义。
格式解析示例:如何检查两个格式字符串是否兼容?
不清楚 OP 想要用宽度和精度做什么,就像在需要 1 个以上参数的"%*.*Lf"
一样。
通过传递格式为printf 编写封面函数的替代方法,void *
将不带格式进行打印,只需将对象转换为宏并使用_Generic
来引导打印函数。 在格式化打印中进行了探索,无需使用 _Generic 指定类型匹配说明符。
正如链接问题的答案所指出的那样,您将不得不编写所有不同的演员表。在你的例子中,这是可行的,因为只有一个void*
,所以强制转换的集合是有限的。
我最终使用了 https://github.com/mpaland/printf 提供的无依赖的printf库。
下载后,基本上就像更改接受"int"等类型的va_arg的所有实例一样简单,改为接受该类型的指针,然后取消引用该值。
例如
... = (int)va_arg(va, int)
已更改为
... = (int)*va_arg(va, int*)
我唯一需要更改的其他代码是关于双精度和浮点数,我必须专门检查是否传递了"lf"或"f"。幸运的是,因为库写得很好而且很容易理解,我注意到在"lf"的情况下已经为我设置了一个标志(即FLAGS_LONG)。
然后,我只需要检查该标志是否已设置,如果是,我将值解释为双精度,否则我将其解释为浮点数。
希望这有助于任何尝试实现类似内容的地方。