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

时间:2011-11-08 07:49:56

标签: c variadic-functions

问题'Pass va_list or pointer to va_list?'有一个答案引用标准(ISO / IEC 9899:1999 - §7.15'变量论证<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;
}

错误消息

当我编译它时(在使用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并且代码不能编译是好的 - 但在修复它之前我想确定。


更新2012-03-30

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

4 个答案:

答案 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函数中实际提供的内容。