我想要一个可以这样调用的函数:
my_printf(int unimportant,
"%-10s", "a string",
"%5.1f", 1.23,
format_string, value_to_format,
/* possibly more pairs like this */
(char *) NULL);
我们可以假设每个格式字符串最多包含一个
%
转义序列。
我想使用
sprintf
系列之一来格式化值,也许会产生类似 "a string , 1.2, …"
的结果。
到目前为止我知道几种行不通的方法。 以下代码显示了基本思想(不完整的草图):
char * my_printf(int uninmportant, ...)
{
va_arg ap;
va_start(ap, unimportant);
while (1) {
char *format = va_arg(ap, char *);
if (!format) break;
vsprintf(buffer, format, ap);
/* append buffer somewhere */
}
}
这不起作用,因为无法保证从
ap
返回时 vsprintf
的有用性。 它可能指向任何地方,也可能指向任何地方。 通常的解决方法是在传递之前将 ap
与 va_copy
复制。 但这在这种情况下没有帮助:
char * my_printf(int uninmportant, ...)
{
va_arg ap;
va_start(ap, unimportant);
while (1) {
char *format = va_arg(ap, char *);
if (!format) break;
va_arg ap_copy;
va_copy(ap_copy, ap);
vsprintf(buffer, format, ap_copy);
/* append buffer somewhere */
/* ?? seek ap forward to point to the next format string ?? */
}
}
现在
vsprintf
不再破坏ap
。 但是“向前寻求ap
以指向下一个格式字符串”似乎相当困难,因为它似乎涉及解析和解释format
的内容,至少足以清楚地知道要传递给va_arg
什么类型。
同样,如果我尝试使用
sprintf
解决问题,我仍然必须解析格式字符串才能理解如何调用 sprintf
:
char * my_printf(int uninmportant, ...)
{
va_arg ap;
va_start(ap, unimportant);
while (1) {
char *format = va_arg(ap, char *);
if (!format) break;
??TYPE?? arg = va_arg(ap, ??TYPE??);
sprintf(buffer, format, arg);
/* append buffer somewhere */
}
}
再次,解决这个问题的唯一方法似乎是解析
format
的值,然后为每种可能的类型提供一个带有手臂的大 switch
。
我不可能是第一个或第一千个想要这样做的人。 这里的传统观念是什么?
我知道某些编译器(例如 GCC)提供了不可移植的扩展来协助实现此目的,例如
parse_printf_format
,但我想要一个更可移植的解决方案。
感谢您的任何建议。
但是“向前寻找
以指向下一个格式字符串”似乎相当困难,因为它似乎涉及解析和解释ap
的内容,至少足以清楚地知道要传递给format
什么类型。va_arg
是的,如果不知道(足够接近)每个元素的类型,您就无法明确地推进
va_list
的元素。 这是 C 可变参数函数和 stdarg.h 宏的基本限制。
解决这个问题的唯一方法似乎是解析格式的值,然后为每种可能的类型设置一个带有手臂的大开关。
是的,这非常正确。 这项工作有点简单,因为
参数将进行左值转换,这会影响所有类型限定符
默认参数升级将对参数值执行,这减少了您需要考虑的类型数量
您只需要兼容类型,不一定完全匹配,尽管在实践中,上述几点可能没有意义。
有一些例外情况,甚至需要兼容的类型,这也可能会减少您需要考虑的类型数量(请参阅 C23 7.16.2.2./2)
您的问题域也可能限制您需要考虑的类型数量。 例如,
printf
系列函数不支持任意指针,而仅支持指向 char
的指针和指向 void
的指针,并且这两个可以与 stdarg 宏互换使用。 在某些情况下(与支持的实际参数值相关),您可能能够将无符号整数类型处理为相应的有符号整数类型,反之亦然。
但是,最终您需要应用所传递参数类型的知识才能继续浏览列表。 这不仅仅是参数大小,还考虑了完全不同的参数传递机制的可能性,例如可以传入哪些寄存器(如果有的话)不同类型的参数。对此没有标准的快捷方式。