va_arg 给出垃圾文本
我做了一个简单的测试用例:
static void va_test(char* str_arg, ...)
{
va_list ap;
va_start(ap, str_arg);
for( ; ; ) {
if (str_arg == NULL)
break;
int n = va_arg(ap,int);
printf("arg: %s,%d\n", str_arg, n);
str_arg = va_arg(ap,char*);
}
va_end(ap);
printf("\n");
}
当我在独立可执行文件中使用 va_test("beer",1,"cofe",2,"juice",3,0)
运行它时,它工作正常。但是当我从非常大的项目可执行文件中调用它时,它会给出一些像这样的垃圾字符串:
arg: bear,1
arg: cofe,2
arg: juice,3
arg: ^X(garbage...),57
我想在调用这个函数之前一定发生了内存混乱,但是我该如何调试它呢?
[编辑] 我稍微更新了描述,因为严格来说,当我向 va_test 传递超过 6 个参数时,就会发生错误。我意识到前六个 64 位参数是通过 amd64 机器中的寄存器传递的,而其他参数是通过堆栈传递的。当 va_arg 尝试从 *overflow_arg_area 获取第一个参数时,就会出现问题。
I made a simple test case:
static void va_test(char* str_arg, ...)
{
va_list ap;
va_start(ap, str_arg);
for( ; ; ) {
if (str_arg == NULL)
break;
int n = va_arg(ap,int);
printf("arg: %s,%d\n", str_arg, n);
str_arg = va_arg(ap,char*);
}
va_end(ap);
printf("\n");
}
When I run it with va_test("beer",1,"cofe",2,"juice",3,0)
in a standalone executable it works fine. But when I call it from my project executable, which is very big, it gives some garbage string like this:
arg: bear,1
arg: cofe,2
arg: juice,3
arg: ^X(garbage...),57
I guess there must be a memory chaos happened before I call this function, but how can I debug it?
[EDIT]
I updated the description a little, since strictly speaking, the bug happens when I passed more than 6 args to va_test. I realized that the first six 64bits args are passed by register in amd64 machine, while other args are passed by stack. The problem happens when va_arg tries to get the first arg from *overflow_arg_area
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
对我来说最有可能的解释是,您所在的系统中
int
0 与char *
0 具有不同的表示形式。这可以在 64 位上系统,其中sizeof(int) == 4
和sizeof(char *) == 8
。尝试将最后一个参数作为
(char *) 0
而不是0
传递,应该没问题。所有其余代码在技术上看起来都是正确的。The explanation that is most likely to me is that you are on a system where the
int
0 has a differeent representation than thechar *
0. This can be on a 64 bit system wheresizeof(int) == 4
andsizeof(char *) == 8
.Try to pass the last argument as
(char *) 0
instead of0
, and you should be fine. All the rest of the code looks technically correct.如果使用 GCC,您可以使用
__attribute__((sentinel))
声明您的va_test
并确保每次调用都以 null 终止,例如我猜您的内存混乱是因为某些调用不是 null 终止的。
If using GCC, you could declare your
va_test
with__attribute__((sentinel))
and ensure that every call occurrence is null-terminated with e.g.I guess your memory chaos is because some call is not null terminated.
来自
va_arg
的 Linux 手册页:您必须找到另一种结束循环的方法,一旦获取了所有参数,您就不能信任
va_arg
的结果。您获得的随机值只是最后一个参数之后堆栈上的值。
From the Linux manual page for
va_arg
:You have to find another way of ending the loop, you can not trust the result of
va_arg
once you fetched all argument.The random values you are getting is simply what's on the stack after the last argument.
除了 Starynkevitch 和 Pileborg 之外,用 while 替换 for 循环可能也是一个好主意,以获得更好的可读性。
(但是将
str_arg != NULL
替换为新的结束条件。) [编辑:谢谢 Jens。]Adding to Starynkevitch and Pileborg, it might also be a good idea to replace the for loop with a while for better readability.
(But replace
str_arg != NULL
with your new end condition.) [Edit: Thank you Jens.]这是 C 标准不是很明确的情况。当可变参数函数以 null 终止时可能会出现问题。对于
NULL
定义为(void *)0
的实现,指针作为参数传递,因此必须使用va_arg
读取指针以避免未定义的行为。如果NULL
被定义为0
(这是 C 标准允许的,但根据我个人的经验,这不是一个流行的定义),那么int 作为参数传递,因此必须使用
va_arg
读取int
以避免未定义的行为。int
和void *
是两种根本不同的类型,通常具有不同的大小,因此可能会导致真正的问题。如果您的 C 实现将
NULL
定义为(void *)0
,请确保在函数调用中提供NULL
而不是仅0
。如果您希望代码尽可能可移植,我会避免传递0
或NULL
,而是提供一个空字符串作为终止符,或类似的东西。This is a case where the C standard is not very explicit. It can be problematic where variadic functions are null-terminated. For implementations where
NULL
is defined as(void *)0
, a pointer is passed as an argument and therefore a pointer must be read usingva_arg
to avoid undefined behaviour. IfNULL
is defined as just0
(which is permitted by the C standard but in my own personal experience is not a popular definition), then anint
is passed as an argument, and therefore anint
must be read usingva_arg
to avoid undefined behaviour.int
andvoid *
are two fundamentally different types that often have different sizes, so it can cause real issues.If your C implementation defines
NULL
as(void *)0
, make sure to provideNULL
in your function call instead of just0
. If you want your code to be as portable as possible, I would avoid passing0
orNULL
and instead maybe provide an empty string as the terminator, or something like that.