gpt4 book ai didi

c - GCC 是否错误地处理了指向传递给函数的 va_list 的指针?

转载 作者:太空宇宙 更新时间:2023-11-04 04:13:07 28 4
gpt4 key购买 nike

问题'Pass va_list or pointer to va_list?'有一个答案引用了标准(ISO/IEC 9899:1999 - §7.15 'Variable arguments , footnote 212)明确表示:

It is permitted to create a pointer to a va_list and pass that pointer to another function, in which case the original function may make further use of the original list after the other function returns.

我正在编译一些代码,可以通过以下示例来说明(实际代码要复杂得多,原始函数比这里显示的要多得多)。

vap.c

#include <stdarg.h>
#include <stdio.h>

static void test_ptr(const char *fmt, va_list *argp)
{
int x;
x = va_arg(*argp, int);
printf(fmt, x);
}

static void test_val(const char *fmt, va_list args)
{
test_ptr(fmt, &args);
}

static void test(const char *fmt, ...)
{
va_list args;
va_start(args, fmt); /* First use */
test_val(fmt, args);
va_end(args);
va_start(args, fmt); /* Second use */
test_ptr(fmt, &args);
va_end(args);
}

int main(void)
{
test("%d", 3);
return 0;
}

错误信息

当我编译它时(在带有 GCC 4.1.2 或 4.5.1 的 RHEL5 上),我收到以下错误消息。请注意 4.5.1 错误消息提供了多少信息 - GCC 团队的改进值得祝贺!

$ gcc --version
gcc (GCC) 4.5.1
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ /usr/bin/gcc --version
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-44)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc -c vap.c
vap.c: In function ‘test_val’:
vap.c:13:5: warning: passing argument 2 of ‘test_ptr’ from incompatible pointer type
vap.c:4:13: note: expected ‘struct __va_list_tag (*)[1]’ but argument is of type ‘struct __va_list_tag **’
$ /usr/bin/gcc -c vap.c
vap.c: In function ‘test_val’:
vap.c:13: warning: passing argument 2 of ‘test_ptr’ from incompatible pointer type
$

我在使用 GCC/LLVM 4.2.1 和 GCC 4.6.1 的 MacOS X Lion 上收到相同的消息:

$ /usr/bin/gcc --version
i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc --version
gcc (GCC) 4.6.1
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$

问题

  • 谁能阐明为什么 test_val() 函数不能将 va_list 作为参数传递给 test_ptr(),而test() 函数(它创建了 va_list)可以吗?

  • GCC 提示 test_val() 中指针的间接传递是否正确?

在这两种情况下,我都可以模糊地看到答案,但我无法简洁地描述它。我认为 test_val() 中的代码正在滥用 va_list 并且代码不会编译很好 - 但我想在我走之前确定修复它。


2012-03-30 更新

这周我去处理有问题的代码。在进行更改之前,我去查找使用了恶意函数的位置 - 但它们不是!因此,我通过删除函数(4 个外部可见但未使用的函数,加上 2 个包含有问题的代码的静态函数)解决了我的编译错误问题。这比必须弄清楚如何处理困惑要简单得多。 (这也解释了为什么从来没有任何证据表明代码会导致运行时问题。)

最佳答案

这是一个已知问题。在某些体系结构(特别是 x86-64)上,va_list 需要比指向堆栈的简单指针更复杂,例如,因为某些参数可能在寄存器中传递或在某些情况下是带外传递的其他方式(参见 this answer 了解 x86-64 上 va_list 的定义)。

在这种架构上,通常将 va_list 设为数组类型,以便将 va_list 类型的参数调整为指针类型,而不是整个结构,只需要传递一个指针。

这不应该违反 C 标准,它只规定 va_list 必须是一个完整的对象类型,甚至明确说明传递 va_list 参数可能不会实际上克隆必要的状态:如果 va_list 对象作为参数传递并在被调用函数中使用,则它们具有不确定的值。

但是即使将 va_list 设为数组类型是合法的,它仍然会导致您遇到的问题:由于类型 va_list 的参数具有“错误”类型,例如struct __va_list_tag * 而不是 struct __va_list_tag [1],它会在数组和指针之间的差异很重要的情况下爆炸。

真正的问题不是 gcc 警告的类型不匹配,而是按指针而不是按值参数传递语义: test_val() 中的 &args 指向中间指针变量而不是 va_list 对象;忽略警告意味着您将在指针变量的 test_ptr() 中调用 va_arg() ,该变量应该返回垃圾(如果幸运,则返回段错误)和损坏堆栈。

一种解决方法是将您的 va_list 包装在一个结构中并传递它。另一种解决方案I've seen in the wild , 甚至 here on SO , 是使用 va_copy 创建参数的本地副本,然后将指针传递给该参数:

static void test_val(const char *fmt, va_list args)
{
va_list args_copy;
va_copy(args_copy, args);
test_ptr(fmt, &args_copy);
va_end(args_copy);
}

这应该在实践中有效,但从技术上讲,它可能是也可能不是未定义的行为,具体取决于您对标准的解释:

如果 va_copy() 被实现为宏,则不执行参数调整,并且 args 不是 va_list 类型可能很重要>。然而,由于 未指定 va_copy() 是宏还是函数,人们可能会争辩说它至少 可能 是一个函数并且参数调整隐含在为宏给出的原型(prototype)中。要求官员澄清甚至提交缺陷报告可能是个好主意。

您还可以使用构建系统通过定义像 HAVE_VA_LIST_AS_ARRAY 这样的配置标志来处理该问题,这样您就可以为您的特定架构做正确的事情:

#ifdef HAVE_VA_LIST_AS_ARRAY
#define MAKE_POINTER_FROM_VA_LIST_ARG(arg) ((va_list *)(arg))
#else
#define MAKE_POINTER_FROM_VA_LIST_ARG(arg) (&(arg))
#endif

static void test_val(const char *fmt, va_list args)
{
test_ptr(fmt, MAKE_POINTER_FROM_VA_LIST_ARG(args));
}

关于c - GCC 是否错误地处理了指向传递给函数的 va_list 的指针?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55092159/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com