我可以按以下方式使用va_list
:
void myself_printf(char* key, char* value, ...) {
char real_key[1024];
char real_value[1024];
va_list args;
va_start(args, value);
// use args twice format key and value
vsnprintf(real_key, 1024000, key, args);
vsnprintf(real_value, 1024000, value, args);
va_end(args);
}
在演示中使用
myself_printf("%d-%s", "%s-%d", 12, "key", "value", 24);
expect: real_key is "12-key", and real_value is "value-24"
答案 0 :(得分:5)
严格来说,必须使用va_copy
,因为vsnprintf
会使args
无效。
void myself_printf(char* key, char* value, ...) {
char real_key[1024000];
char real_value[1024000];
va_list args, args2;
va_start(args, value);
va_copy(args2, args);
// use args twice format key and value
vsnprintf(real_key, 1024000, key, args);
vsnprintf(real_value, 1024000, value, args2);
va_end(args);
va_end(args2);
}
这是va_copy
设计的目的。
如上所述,这是大量的堆栈空间,尽管它在典型的堆栈大小之内。如果可用,请考虑使用vasprintf
。
n1548§7.16
对象ap可以作为参数传递给另一个函数;如果该函数使用参数ap调用va_arg宏,则调用函数中ap的值不确定...
换句话说,将args
传递给vsnprintf
后,便无法移植。
在脚注281中对此进行了澄清:
当函数vfprintf,vfscanf,vprintf,vscanf,vsnprintf,vsprintf和vsscanf调用va_arg宏时,返回后的arg的值不确定。
虽然va_list
是按值传递的,但这并不意味着它实际上是按值传递的,或者va_list
本身封装了其所有状态。 C中使用typedef的常见技巧是将它们声明为1元素数组:
typedef int my_type[1];
由于my_type
在通过函数传递时会衰减为指针类型,因此它仅出现才能通过值传递。
#include <stdio.h>
#include <stdarg.h>
void func(const char *msg, ...) {
va_list ap;
va_start(ap, msg);
vfprintf(stdout, msg, ap);
vfprintf(stdout, msg, ap);
va_end(ap);
}
int main(int argc, char **argv) {
func("%d + %d = %d\n", 2, 3, 5);
return 0;
}
在我的计算机上,输出为:
2 + 3 = 5 590862432 + -1635853408 = 1586038440
答案 1 :(得分:3)
作为Dietrich Epp在answer中建议的使用va_copy()
的替代方法,您可以简单地两次使用va_start()
和va_end()
。
void myself_printf(char *key_fmt, char *value_fmt, ...)
{
char real_key[1024];
char real_value[1024];
va_list args;
va_start(args, value);
vsnprintf(real_key, sizeof(real_key), key_fmt, args);
va_end(args);
vs_start(args, value);
vsnprintf(real_value, sizeof(real_key), value_fmt, args);
va_end(args);
…do something useful with real_key and real_value…
}
问题的原始版本使用char real_key[1024000];
,类似地用于real_value
。在Windows上将几乎2 MiB的数据分配给Windows不能可靠地工作(通常只有1 MiB的限制),而在Unix系统上(堆栈大小通常为8 MiB)会占用大量空间。小心点!
如果将va_copy()
作为参数传递给函数,则需要使用va_list
。例如:
void myself_printf(char* key, char* value, ...)
{
va_list args;
va_start(args, value);
myself_vprintf(key, value, args);
va_end(args);
}
void myself_vprintf(char *key_fmt, char *value_fmt, va_list args1)
{
char real_key[1024];
char real_value[1024];
va_list args2;
va_copy(args2, args1);
vsnprintf(real_key, sizeof(real_key), key_fmt, args1);
vsnprintf(real_value, sizeof(real_value), value_fmt, args2);
va_end(args2);
…do something useful with real_key and real_value…
}
va_end()
的规范说:
va_end
宏有助于从函数的正常返回,该函数的变量参数列表由va_start
宏的扩展引用,或者包含va_copy
宏的扩展的函数,它初始化了va_list ap
。va_end
宏可以修改ap
,使其不再可用(无需通过va_start
或va_copy
宏重新初始化)。如果没有对va_start
或va_copy
宏的相应调用,或者如果在返回之前未调用va_end
宏,则行为是不确定的。
请注意,vfprintf()
函数的规范包括脚注288,其说明:
随着功能
vfprintf
,vfscanf
,vprintf
,vscanf
,vsnprintf
,vsprintf
和vsscanf
的调用,va_arg
宏,返回后的arg
的值不确定。
当然,脚注也不是规范性的,但这强烈表明该函数应使用va_arg
,因此,如问题所示重复使用arg
会导致未定义的行为,除非存在是对va_end
和另一个va_start
的干预,或对va_copy
(及其匹配的va_end
的使用)的调用。