va_arg 给出垃圾文本

发布于 2024-12-15 10:26:52 字数 747 浏览 1 评论 0原文

我做了一个简单的测试用例:

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

兰花执着 2024-12-22 10:26:52

对我来说最有可能的解释是,您所在的系统中 int 0 与 char * 0 具有不同的表示形式。这可以在 64 位上系统,其中 sizeof(int) == 4sizeof(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 the char * 0. This can be on a 64 bit system where sizeof(int) == 4 and sizeof(char *) == 8.

Try to pass the last argument as (char *) 0 instead of 0, and you should be fine. All the rest of the code looks technically correct.

满天都是小星星 2024-12-22 10:26:52

如果使用 GCC,您可以使用 __attribute__((sentinel)) 声明您的 va_test 并确保每次调用都以 null 终止,例如

 #define va_test(Fmt,...) va_test(Fmt,__VA_ARGS__,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.

 #define va_test(Fmt,...) va_test(Fmt,__VA_ARGS__,NULL)

I guess your memory chaos is because some call is not null terminated.

一曲琵琶半遮面シ 2024-12-22 10:26:52

来自 va_arg 的 Linux 手册页:

If there is no next argument, or if type is not compatible with the type of the
actual next  argument  (aspromoted according to the default argument promotions),
random errors will occur.

您必须找到另一种结束循环的方法,一旦获取了所有参数,您就不能信任 va_arg 的结果。

您获得的随机值只是最后一个参数之后堆栈上的值。

From the Linux manual page for va_arg:

If there is no next argument, or if type is not compatible with the type of the
actual next  argument  (aspromoted according to the default argument promotions),
random errors will occur.

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.

咋地 2024-12-22 10:26:52

除了 Starynkevitch 和 Pileborg 之外,用 while 替换 for 循环可能也是一个好主意,以获得更好的可读性。

while (str_arg != NULL) {
   /* Do stuff */
}

(但是将 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.

while (str_arg != NULL) {
   /* Do stuff */
}

(But replace str_arg != NULL with your new end condition.) [Edit: Thank you Jens.]

囍孤女 2024-12-22 10:26:52

这是 C 标准不是很明确的情况。当可变参数函数以 null 终止时可能会出现问题。对于 NULL 定义为 (void *)0 的实现,指针作为参数传递,因此必须使用 va_arg 读取指针以避免未定义的行为。如果 NULL 被定义为 0 (这是 C 标准允许的,但根据我个人的经验,这不是一个流行的定义),那么 int 作为参数传递,因此必须使用 va_arg 读取 int 以避免未定义的行为。 intvoid * 是两种根本不同的类型,通常具有不同的大小,因此可能会导致真正的问题。

如果您的 C 实现将 NULL 定义为 (void *)0,请确保在函数调用中提供 NULL 而不是仅 0。如果您希望代码尽可能可移植,我会避免传递 0NULL ,而是提供一个空字符串作为终止符,或类似的东西。

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 using va_arg to avoid undefined behaviour. If NULL is defined as just 0 (which is permitted by the C standard but in my own personal experience is not a popular definition), then an int is passed as an argument, and therefore an int must be read using va_arg to avoid undefined behaviour. int and void * 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 provide NULL in your function call instead of just 0. If you want your code to be as portable as possible, I would avoid passing 0 or NULL and instead maybe provide an empty string as the terminator, or something like that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文