43

在这里,我找到了如何在 C 中使用可变参数的示例。

#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

4 回答 4

44

请记住,参数是在堆栈上传递的。该函数包含用正确的堆栈指针va_start初始化的“魔术”代码。va_list必须传递函数声明中的最后一个命名参数,否则它将不起作用。

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


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

于 2013-04-03T10:25:41.420 回答
8

但是为什么默认不设置为开头呢?

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

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

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

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

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

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

在大多数架构va_end上没有做任何相关的事情。但是有些架构具有复杂的参数传递语义,va_start甚至可能会 malloc 内存,然后您需要va_end释放该内存。

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

于 2013-04-03T13:03:12.610 回答
6

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

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

于 2013-04-03T10:25:33.557 回答
3

它是 C 宏。va_start将内部指针设置为第一个元素的地址。va_end清理va_list。如果有va_start代码而没有va_end- 它是 UB。

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

于 2013-04-03T10:25:53.620 回答