Demo
// 宏定义在stdarg.h,用来确定可变参数列表中每个参数的内存地址
void va_start( va_list arg_ptr, prev_param);
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
void simple_va_fun(int i,...) {
va_list arg_ptr;
int j=0;
va_start(arg_ptr, i);
j=va_arg(arg_ptr, int);
va_end(arg_ptr);
printf("%d %d\n", i, j);
return;
}
//在程序中可以这样调用:
simple_va_fun(100);
simple_va_fun(100,200);
从这个函数的实现可以看到,我们使用可变参数有以下步骤:
1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.。
2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.。
3)然后用va_arg返回可变的参数,并赋值给整数j。va_arg的第二个参数是你要返回的参数的类型,这里是int型。
4)最后用va_end宏结束可变参数的获取。如果函数有多个可变参数的,依次调用va_arg获取各个参数.
可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型。
在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如
⑴在固定参数中设标志– printf函数就是用这个办法。
⑵在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾
原理
//VC++ x86平台
typedef char * va_list;
// 子节对齐
#define _INTSIZEOF(n) \
((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
//运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
//首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址(图2).然后用*取得这个地址的内容(参数值)赋给j.
#define va_arg(ap,t) \
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
高地址|-----------------------------|
|函数返回地址 |
|-----------------------------|
|.......|
|-----------------------------| <--va_arg后ap指向
|第n个参数(第一个可变参数)|
|-----------------------------| <--va_start后ap指向
|第n-1个参数(最后一个固定参数)|
低地址|-----------------------------| <-- &v
实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定 详情点击
①函数栈的生长方向
②参数的入栈顺序
③CPU的对齐方式
④内存地址的表达方式
va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。
如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现