在C中使用可变参数的一个例子

在这里,我find了一个如何在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了, 我们为什么要改变它?

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

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


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

va_start生成可变参数列表。 您总是传递最后一个命名的函数参数作为第二个参数。 这是因为你需要提供堆栈中有关位置的信息,其中可变参数开始,因为参数被压入堆栈,编译器无法知道variables参数列表的开始位置(没有区别)。

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

但是为什么默认情况下它不是开始的?

也许是因为编译器不够聪明的历史原因。 也许是因为你可能有一个可变参数函数原型,实际上并不关心可变参数,设置可变参数在那个特定的系统上是昂贵的。 也许是因为你做va_copy操作比较复杂,或者你想多次重启参数,并多次调用va_start

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

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

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

作为你的问题的第二部分:不,编译器不知道有多less个参数被发送到函数。 它甚至可能不在同一个编译单元中,甚至不在同一个程序中,编译器也不知道该函数将如何被调用。 想象一下,像printf这样的可变参数函数库。 编译libc时,编译器不知道程序何时和如何调用printf 。 在大多数ABI中(ABI是如何调用函数的,如何传递参数等),没有办法找出函数调用得到多less个参数。 将这些信息包含在函数调用中是很浪费的,而且几乎是不需要的。 所以你需要有一种方法来告诉varargs函数有多less个参数。 访问超过实际传递的参数数量的va_arg是未定义的行为。

那么我不清楚为什么我们使用va_end(ap)。 它改变了什么?

在大多数架构上, va_end并没有做任何相关的事情。 但是有一些架构使用复杂的parameter passing语义,而va_start甚至可能会使用malloc内存,那么您需要使用va_end来释放内存。

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

这是Cmacros。 va_start将内部指针设置为第一个元素的地址。 va_end清理va_list 。 如果代码中有va_start ,并且没有va_end – 它是UB。

在本标准中,ISO C将第二个参数放在头中的va_start()macros上的限制是不同的。 参数parmN是函数定义的可变参数列表中最右边的参数的标识符(紧挨着…之前的那个参数)。 如果使用函数,数组或引用types声明参数parmN,或者使用与传递没有参数的parameter passing的types不兼容的types,则行为是未定义的。