在C中使用varargs的一个示例

在C中使用varargs的一个示例,c,syntax,variadic-functions,C,Syntax,Variadic Functions,我发现了一个在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++)

我发现了一个在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;
}
#包括
双平均值(整数计数,…)
{
va_列表ap;
int j;
双tot=0;
va_start(ap,count);//需要最后一个固定参数(获取地址)

对于(j=0;jva_start初始化变量参数列表。您总是将最后一个命名的函数参数作为第二个参数传递给。这是因为您需要提供有关堆栈中变量参数开始位置的信息,因为参数被推到堆栈上,编译器无法知道起始位置变量参数列表的g(没有区别)


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

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

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



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

这是C宏。
va_start
设置指向第一个元素地址的内部指针。
va_end
cleanup
va_列表
。如果代码中有
va_开始
,但没有
va_结束
,则为UB

ISO C对标头中va_start()宏的第二个参数的限制 参数parmN是最右边参数的标识符 在函数定义的变量参数列表中(就在…)之前。如果 parmN是用函数、数组或引用类型声明的,或者是用与 输入在传递没有参数的参数时产生的结果,行为未定义。

但为什么默认情况下不设置为开始

可能是因为编译器不够聪明时的历史原因。可能是因为您可能有一个varargs函数原型,它实际上并不关心varargs,并且在特定系统上设置varargs的成本很高。可能是因为您执行
va_copy
的操作更为复杂,或者可能您需要nt多次重新开始使用参数,并多次调用
va_start

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

第二,我不清楚为什么我们需要把计数作为一个参数。C++不能自动确定参数的数量吗?< /P> 这不是所有的

count
是什么。它是函数的最后一个命名参数。
va_start
需要它来找出varargs在哪里。很可能这是由于旧编译器的历史原因。我不明白为什么它今天不能以不同的方式实现

作为问题的第二部分:不,编译器不知道向函数发送了多少个参数。它甚至可能不在同一个编译单元中,或者甚至不在同一个程序中,编译器也不知道如何调用函数。想象一下,在编译libc时,有一个带有varargs函数的库,如
printf
编译器不知道程序何时以及如何调用
printf
无法确定函数调用得到了多少个参数。在函数调用中包含这些信息是浪费的,而且几乎不需要这些信息。因此,您需要有一种方法来告诉varargs函数它得到了多少个参数。访问
va_arg
超出实际传递的参数数量是未定义的行为r

那么我不清楚为什么我们要使用va_end(ap)。它有什么变化

在大多数体系结构上,
va_end
不做任何相关的事情。但是有些体系结构具有复杂的参数传递语义,
va_start
甚至可能会占用malloc内存,那么您需要
va_end
来释放内存


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

Roman:这与“编译器不够聪明”完全无关。变量函数的性质是,传递的参数数量在运行时确定,但编译器在编译时运行。因此,任何编译器都不可能知道传递给变量函数的参数数量。在编译时,唯一的方法是保留一个au自动“隐形”参数计数,并让编译器始终初始化它,但C的设计者选择不以这种方式实现可变函数。@trijezdci Eh?参数的数量与哪个vararg参数将是第一个参数有什么关系?我看不出编译器没有理由不能在
foo(int a,…)中弄清楚这一点
varargs在
a
之后开始。这是第一个问题,在编译时是众所周知的。我在回答中提到了编译时未知的参数计数,所以我不理解为什么您觉得有必要重复它。编译器可以在编译时计数args,但函数它本身在运行时执行,将该计数从编译时传递到运行时的唯一方法是将其作为ex传递