问题'Pass va_list or pointer to va_list?'有一个答案引用标准(ISO / IEC 9899:1999 - §7.15'变量论证<stdarg.h>
,脚注212)明确地说:
允许创建指向
va_list
的指针并将该指针传递给另一个函数,在这种情况下,原始函数可以在另一个函数返回后进一步使用原始列表。
我正在编译一些代码,可以举例说明(实际代码非常复杂,原始函数比这里显示的工作要多得多)。
#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
函数1}})可以?
GCC是否正确抱怨test_val()
中指针的间接传递?
在这两种情况下,我都可以模糊地看到答案,但我无法简洁地描述。我认为test_val()
中的代码滥用va_list
并且代码不能编译是好的 - 但在修复它之前我想确定。
本周我去处理有问题的代码。在进行更改之前,我去找到使用了miscreant函数的地方 - 而且它们不是!所以,我通过删除函数解决了我的编译错误问题(4个外部可见但未使用的函数,加上2个包含有问题代码的静态函数)。这比弄清楚如何处理混乱要简单得多。 (这也解释了为什么代码不会导致运行时问题的证据。)
答案 0 :(得分:19)
这是一个已知问题。在某些体系结构(特别是x86-64)中,va_list
需要比指向堆栈的简单指针更复杂,例如,因为某些参数可能在寄存器中传递或以其他方式传递到带外(有关x86-64上va_list
的定义,请参阅this answer。
在这样的体系结构中,通常使va_list
成为数组类型,以便将类型va_list
的参数调整为指针类型,而不是整个结构,只需要一个指针通过了。
这不应违反C标准,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参数传递语义:&args
中的test_val()
指向中间指针变量而不是{ {1}}对象;忽略警告意味着你将在va_list
中对指针变量调用va_arg()
,这应该返回垃圾(如果幸运的话,还会丢失段错误)并破坏堆栈。
一种解决方法是将test_ptr()
包裹在一个结构中,然后传递它。另一个解决方案I've seen in the wild,即here on SO,是使用va_list
创建参数的本地副本,然后传递指向该参数的指针:
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
答案 1 :(得分:7)
问题并非针对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;
};
并且没有输入问题的输入问题。
答案 2 :(得分:6)
正如其他人所说,当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);
}
答案 3 :(得分:3)
我认为va_list
必须声明为数组类型,当声明为函数的参数时,它会“归结”为指针类型。因此,应用于&
中的va_list
类型的test_val
会产生指向指针类型的指针,而不是指向数组类型的指针,但是,test_ptr
函数声明了一个其参数作为指向数组类型的指针,这是test
函数中实际提供的内容。