C/C++可变参数va_list

时间:2022-07-07 19:40:01

VA_LIST 是在C语言中解决变参问题的一组宏,可变参数即表示参数个数、类型可以变化。可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。
可变参数是实现printf(),sprintf()等函数的关键之处,也可以用可变参数来对任意数量的数据进行求和,求平均值带来方便(不然就用数组或每种写个重载)。

#include <stdarg.h> 
//头文件(必需)

int AveInt(int v,...)

{

int ReturnValue=0;

int i=v;

va_list ap ;

va_start(ap,v);

while(i>0)

{

ReturnValue+=va_arg(ap,int) ;

i--;

}

va_end(ap);

return ReturnValue/=v;

}

C语言的函数形参是从右向左压入堆栈的,以保证栈顶是第一个参数,而且x86平台内存分配顺序是从高地址到低地址。因此似函数AVEInt(int var1,int var2,…,int varN)内存分配大致上是这样的:(可变参数在中间)
栈区:
|栈顶 低地址
|第一个参数var1 <– &v
|第二个参数var2 <– va_start(ap,v)后ap指向地址
|…
|函数的最后varN
|…
|函数的返回地址
|…
|栈底 高地址

va_list ap ; 定义一个va_list变量ap
va_start(ap,v) ;执行ap = (va_list)&v + _INTSIZEOF(v),ap指向参数v之后的那个参数的地址,即 ap指向第一个可变参数在堆栈的地址。
va_arg(ap,t) , ( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )取出当前ap指针所指的值,并使ap指向下一个参数。 ap+= sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型指针,这正是第一个可变参数在堆栈里的地址。然后 用取得这个地址的内容。

va_end(ap) ; 清空va_list ap。

使用VA_LIST应该注意的问题:
(1)因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.
(2)另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码。
(3)由于参数的地址用于VA_START宏,所以参数不能声明为寄存器变量,或作为函数或数组类型。