第一:什么是可变参数
int printf(const char* format, ...);
看到printf的定义大家就知道了,只有一个固定的const char*参数,后面的都是不定长的参数列表了。
第二:自己写一个可变参数函数
1.参数形参方式,跟printf类似,第一个为固定参数,后面的用...代替;
2.包含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);
va是variable argument可变参数的意思。
3.函数里面定义一个va_list类型的变量,它是存储参数地址的指针,因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。
4.用va_start宏初始化3中定义的va_list类型变量,
5.用va_arg宏获得下一个参数的值
6.设定结束条件。
例子程序:
1#include<stdio.h>
2#include<stdarg.h> //required by variable argument list
3
4void simple_va_function(int start, ){
5 va_list arg_ptr;
6 int nArgValue = start;
7 int nArgCount = 0; //count of arguments
8 va_start(arg_ptr, start); //to obtain beginning address of variable arguments base on the given arguemnt
9 do{
10 nArgCount++;
11 printf("The %d argument is %d.\n", nArgCount, nArgValue);
12 nArgValue = va_arg(arg_ptr, int);
13 }while(nArgValue != -1);
14 va_end(arg_ptr);
15 return;
16}
17
18int main(){
19 simple_va_function(1, 2, 3, 4, -1); //need to set a flag to specify the end of argument list
20 return 0;
21}
第三:可变参数的编译器实现原理,这几个宏的实现。
首先列出stdarg.h里面的宏定义(注意这个宏定义与硬件平台和编译器有关,这里是VC6的宏定义):
typedef char * va_list;
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
其中va_list被定为一般指针,有些机器使用void*,有些使用char*,反正都可以啦。
_INTSZEOF(n)是考虑了内存对齐后的变量占用空间,自己考虑一下,代进去小于4字节的类型进去算一下,就知道在这个平台上(32位机),内存对齐到4个字节。
va_start就是求得第一个可变参数的地址
va_arg是求的下一个可变参数的地址存的值
va_end只是简单地将指针置零
第四:小结
1.标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。
2.在实际应用的代码中,程序员必须自己考虑确定参数数目的办法
1)在固定参数设定标志,例如printf的实现,有多少个%号则表明后面多少个参数
2)多设置一个可变参数标识参数列表结束
3.实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:
①函数栈的生长方向
②参数的入栈顺序
③CPU的对齐方式
④内存地址的表达方式,用void*表示一般地址还是char*
结合源代码,我们可以看出va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。