GCC是否error handling了传递给函数的va_list的指针?

问题’传递va_list或指向va_list的指针?’ 有一个答案引用标准(ISO / IEC 9899:1999 – §7.15’变量参数 ,脚注212)明确地说:

允许创建指向va_list的指针并将该指针传递给另一个函数,在这种情况下,原始函数可以在另一个函数返回后进一步使用原始列表。

我正在编译一些代码,可以举例说明(实际代码非常复杂,原始函数比这里显示的工作要多得多)。

vap.c

 #include  #include  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; } 

错误消息

当我编译它时(在RHEL5上使用GCC 4.1.2或4.5.1),我收到以下错误消息。 请注意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

我本周去处理有问题的代码。 在进行更改之前,我去找到使用了miscreant函数的地方 – 而且它们不是! 所以,我通过删除函数解决了我的编译错误问题(4个外部可见但未使用的函数,加上2个包含有问题代码的静态函数)。 这比弄清楚如何处理混乱要简单得多。 (这也解释了为什么从来没有任何证据表明代码导致运行时问题。)

这是一个已知问题。 在某些体系结构(特别是x86-64)中, va_list需要比指向堆栈的简单指针更复杂,例如因为某些参数可能在寄存器中传递或以其他方式传递到带外(请参阅此答案 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警告的类型不匹配,而是by-pointer而不是by-value参数传递语义: test_val() &args指向中间指针变量而不是va_list对象; 忽略警告意味着你将在test_ptr()调用指针变量上的va_arg() ,它应该返回垃圾(如果幸运的话,还是段错误)并破坏堆栈。

一种解决方法是将va_list包装在一个结构中,然后传递它。 我在野外看到的另一个解决方案,即使是在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()是宏还是函数,可能会认为它至少可以是函数,并且在为宏指定的原型中隐式假设参数调整。 要求官员澄清甚至提交缺陷报告可能是个好主意。

您还可以使用构建系统通过定义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)); } 

问题不是va_list特有的。 以下代码会产生类似的警告:

 typedef char *test[1]; void x(test *a) { } void y(test o) { x(&o); } 

问题源于C处理也是数组的函数变量的方式,可能是由于数组作为引用而不是值传递。 o的类型与test类型的局部变量的类型不同,在这种情况下: char ***而不是char *(*)[1]

回到手头的原始问题,解决它的简单方法是使用容器结构:

 struct va_list_wrapper { va_list v; }; 

并且没有输入问题的输入问题。

正如其他人所指出的那样,当va_list是一个数组类型时,这个问题就会出现。 这是标准允许的,它只表示va_list必须是“对象类型”

您可以像这样修复test_val()函数:

 static void test_val(const char *fmt, va_list args) { va_list args_copy; /* Note: This seemingly unnecessary copy is required in case va_list * is an array type. */ va_copy(args_copy, args); test_ptr(fmt, &args_copy); va_end(args_copy); } 

我认为va_list必须声明为数组类型,当声明为函数的参数时,它会“归结”为指针类型。 因此,应用于test_valva_list类型会产生指向指针类型的指针,而不是指向数组类型的指针,但是, test_ptr函数将其参数之一声明为指向数组类型的指针,这是实际提供的在testfunction中。