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

Jon*_*ler 25 c variadic-functions

问题'传递va_list或指向va_list的指针?' 有一个答案引用标准(ISO/IEC 9899:1999 - §7.15'变量论点<stdarg.h>,脚注212)明确地说:

允许创建指向a的指针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;
}
Run Code Online (Sandbox Code Playgroud)

错误消息

当我编译它时(在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
$ 
Run Code Online (Sandbox Code Playgroud)

我在使用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.

$
Run Code Online (Sandbox Code Playgroud)

问题

  • 有人可以明确说明为什么test_val()函数不能va_list传递作为参数传递test_ptr(),而test()函数(创建它va_list)可以吗?

  • GCC是否正确抱怨间接传递指针test_val()

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


更新2012-03-30

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

Chr*_*oph 19

这是一个已知问题.在某些体系结构(尤其是X86-64),va_list需要比简单的指针栈更复杂,例如,因为一些参数可能在寄存器中传递或超出带外的其他方式(见这个答案va_list关于x86-64的定义.

在这样的体系结构中,通常创建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发出警告,但通过指针,而不是按值参数传递语义:&argstest_val()指向中间指针变量,而不是va_list对象; 无视警告意味着你调用va_arg()test_ptr()的指针变量,它应该返回垃圾(或者,如果你是幸运的段错误)和腐败的堆栈.

一种解决方法是将您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); 
}
Run Code Online (Sandbox Code Playgroud)

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

如果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));
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*oni 7

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

typedef char *test[1];

void x(test *a)
{
}

void y(test o)
{
    x(&o);
}
Run Code Online (Sandbox Code Playgroud)

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

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

struct va_list_wrapper {
    va_list v;
};
Run Code Online (Sandbox Code Playgroud)

并且没有输入问题的打字问题.


caf*_*caf 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);
}
Run Code Online (Sandbox Code Playgroud)