首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >是不是GCC错误地处理了传递给函数的va_list指针?

是不是GCC错误地处理了传递给函数的va_list指针?
EN

Stack Overflow用户
提问于 2011-11-08 15:49:56
回答 4查看 11.7K关注 0票数 28

问题'Pass va_list or pointer to va_list?'有一个引用标准(ISO/IEC9899:1999-§7.15 'Variable arguments <stdarg.h>,脚注212)的答案,明确表示:

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

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

vap.c

代码语言:javascript
复制
#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团队的改进!

代码语言:javascript
复制
$ 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:

代码语言:javascript
复制
$ /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个包含有问题代码的静态函数)解决了我的编译错误问题。这比必须弄清楚如何处理混乱要简单得多。(这也解释了为什么从来没有任何由代码引起的运行时问题的证据。)

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 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创建参数的本地副本,然后传递指向该副本的指针:

代码语言:javascript
复制
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,这样您就可以为您的特定体系结构做正确的事情:

代码语言:javascript
复制
#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));
}
票数 23
EN

Stack Overflow用户

发布于 2011-11-08 16:07:23

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

代码语言:javascript
复制
typedef char *test[1];

void x(test *a)
{
}

void y(test o)
{
    x(&o);
}

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

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

代码语言:javascript
复制
struct va_list_wrapper {
    va_list v;
};

向它传递一个点也不会有任何打字问题。

票数 8
EN

Stack Overflow用户

发布于 2011-11-08 18:18:24

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

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

代码语言:javascript
复制
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);
}
票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/8047362

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档