(1)函数使用的时候会首先定义一个va_list型的变量,这个变量是存储参数地址的指针,因为得到参数地址之后,再结合参数类型,才能得到参数的值,va_list被定义为char*,因为目前我们使用的PC机上,字符指针类型用来存储内存单元地址,有些机器上也会定义为void*类型;
(2)定义_INTSIZEOF(n)主要是为了某些需要内存对齐的系统,这个宏的目的就是为了得到最后一个固定参数的实际内存大小;一般说来它和sizeof一样;
(3)
va_start(va_list,type)的作用是指向第一个可变参数,va_start的定义为&v + _INTSIZEOF(v),这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址;
(4)va_arg(va_list,type)的作用是根据指定的的参数类型取得本参数的值,并使va_list指向像一个参数的起始地址,相当于进栈操作,
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
这个宏做了两个事情,
①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值
②计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。
(5)va_end(va_list)的作用是把va_list指针清为NULL,x86平台定义为ap=(char*)0;使va_list不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型.
函数体内可以多次遍历这些参数,但是都必须以va—start开始,并以va—end结尾。
va_arg()整型只能用int表示,不能用short否则编译会有警告,执行会出错;
浮点数只能用double,不能用float,否则结果同上;
指针类型可以使用;
使用可变参数要注意的问题:
1.标准c库中的三个宏作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道实际参数的数目的;
①在固定参数中设置标志——printf函数就是用第一个固定字符串参数确定的;
②在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾;
无论什么方法,程序员都应该在文档中告诉调用者自己约定;
2.实现可变参数的要点是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:函数栈的生长方向,参数的入栈顺序,cpu对齐方式,内存地址表达方式;va_list的实现方式是由内存地址的表达方式决定的,_INTSIZEOF(n)的引入规则是由cpu对齐方式决定的,它和第一个和第二个一起决定了va_start的实现;
3.取得地址后,再结合参数类型,就可正确处理参数;
参考资料:
《the c programming language》
http://blog.163.com/fenglanghai@126/blog/static/33568779201192214147570/