问题'Pass va_list or pointer to va_list?'有一个引用标准(ISO/IEC9899:1999-§7.15 'Variable arguments <stdarg.h>
,脚注212)的答案,明确表示:
允许创建指向
va_list
的指针并将该指针传递给另一个函数,在这种情况下,在其他函数返回后,原始函数可以进一步使用原始列表。
我正在编译一些代码,这些代码可以用下面的代码举例说明(真正的代码非常复杂,原始函数做的工作比这里显示的多得多)。
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;
}
错误消息
当我编译它(在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
$
我在MacOS X Lion上得到了相同的消息,使用的是GCC/LLVM 4.2.1和GCC 4.6.1:
$ /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()
函数不能将作为参数传递给test_ptr()
的va_list
传递给test_ptr()
,而test()
函数(它创建了va_list
)却可以吗?test_val()
中间接传递指针的做法是正确的
在这两种情况下,我都能模糊地看到答案,但我不能简洁地描述它。我认为test_val()
中的代码滥用了va_list
,代码不能编译是件好事--但在我修复它之前,我想确认一下。
更新2012-03-30
这周我去处理了有问题的代码。在进行更改之前,我去找出了恶意函数的使用位置--它们并没有使用!因此,我通过删除函数(4个外部可见但未使用的函数,以及2个包含有问题代码的静态函数)解决了我的编译错误问题。这比必须弄清楚如何处理混乱要简单得多。(这也解释了为什么从来没有任何由代码引起的运行时问题的证据。)
发布于 2011-11-08 18:19:11
这是一个已知的问题。在某些体系结构(特别是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警告的类型不匹配,而是按指针而不是按值传递参数的语义:test_val()
中的&args
指向中间指针变量,而不是va_list
对象;忽略警告意味着您将在指针变量上调用test_ptr()
中的va_arg()
,这将返回垃圾(如果幸运的话,也可以是segfault )并损坏堆栈。
一种解决办法是将您的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()
是宏还是函数,有人可能会争辩说,它至少可以是一个函数,参数调整在给定宏的原型中隐含地假设了。这可能是一个好主意,要求官员澄清,甚至提交缺陷报告。
您还可以使用您的构建系统来处理此问题,方法是定义一个配置标志,如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));
}
发布于 2011-11-08 16:07:23
问题不是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;
};
向它传递一个点也不会有任何打字问题。
发布于 2011-11-08 18:18:24
正如其他人所指出的,当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);
}
https://stackoverflow.com/questions/8047362
复制相似问题