在C中使用varargs的一个例子

时间:2013-04-03 10:20:16

标签: c syntax variadic-functions

Here我找到了一个如何在C中使用varargs的例子。

#include <stdarg.h>

double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count); //Requires the last fixed parameter (to get the address)
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
    va_end(ap);
    return tot/count;
}

我只能在某种程度上理解这个例子。

  1. 我不清楚为什么要使用va_start(ap, count);。据我所知,通过这种方式我们将迭代器设置为它的第一个元素。但是为什么默认情况下它没有设置到开头?

  2. 我不清楚为什么我们需要以count作为论据。 C不能自动确定参数的数量?

  3. 我不清楚为什么要使用va_end(ap)。它有什么变化?它是否将迭代器设置为列表的末尾?但它是不是通过循环设置到列表的末尾?而且,为什么我们需要它呢?我们不再使用ap了;我们为什么要改变它?

4 个答案:

答案 0 :(得分:34)

请记住,参数是在堆栈上传递的。 va_start函数包含&#34; magic&#34;使用正确的堆栈指针初始化va_list的代码。 必须传递函数声明中的最后一个命名参数,否则它将无效。

va_arg做的是使用这个保存的堆栈指针,并为所提供的类型提取正确的字节数,然后修改ap,使其指向堆栈上的下一个参数。


实际上,这些函数(va_startva_argva_end)实际上不是函数,而是实现为预处理器宏。实际的实现还取决于编译器,因为不同的编译器可以具有不同的堆栈布局以及它如何在堆栈上推送参数。

答案 1 :(得分:6)

va_start初始化变量参数列表。您始终将最后命名的函数参数作为第二个参数传递。这是因为你需要提供有关堆栈中位置的信息,其中变量参数开始,因为参数被推送到堆栈上,编译器无法知道变量参数列表的开头在哪里(没有区别)。

对于va_end,它用于在va_start调用期间释放为变量参数列表分配的资源。

答案 2 :(得分:6)

  

但是为什么默认情况下它没有设置为开头?

也许是因为编译器不够聪明的历史原因。也许是因为你可能有一个varargs函数原型,它实际上并不关心varargs,并且设置varargs在特定系统上碰巧是昂贵的。也许是因为您执行的操作更复杂va_copy或者您希望多次重新使用参数并多次调用va_start

简短版本是:因为语言标准是这样说的。

  

其次,我不清楚为什么我们需要把数作为一个论点。 C ++不能自动确定参数的数量吗?

这不是count的全部内容。它是函数的最后一个命名参数。 va_start需要它来确定varargs的位置。这很可能是出于旧编译器的历史原因。我不明白为什么今天不能以不同的方式实施。

作为问题的第二部分:不,编译器不知道向函数发送了多少个参数。它甚至可能不在同一个编译单元甚至是同一个程序中,编译器也不知道如何调用该函数。想象一个具有类似printf的varargs函数的库。编译libc时,编译器不知道程序何时以及如何调用printf。在大多数ABI上(ABI是如何调用函数,如何传递参数等的约定),没有办法找出函数调用得到多少个参数。将这些信息包含在函数调用中并且几乎不需要它是浪费的。因此,您需要有一种方法来告诉varargs函数它获得了多少参数。访问va_arg超出实际传递的参数数量是未定义的行为。

  

然后我不清楚为什么我们使用va_end(ap)。它有什么变化?

在大多数架构va_end上都没有做任何相关的事情。但是有一些架构具有复杂的参数传递语义,而va_start甚至可能具有malloc内存,那么你需要va_end来释放内存。

这里的简短版本也是:因为语言标准是这样说的。

答案 3 :(得分:3)

这是C宏。 va_start设置指向第一个元素地址的内部指针。 va_end清理va_list。如果代码中有va_start且没有va_end - 那就是UB。

ISO C对标头中的va_start()宏的第二个参数的限制 在这个国际标准中有所不同。参数parmN是最右边参数的标识符 在函数定义的变量参数列表中(恰好在......之前)。如果是参数 parmN使用函数,数组或引用类型声明,或者使用与...不兼容的类型声明 传递没有参数的参数时得到的类型,行为是未定义的。