转自:https://blog.csdn.net/hailongchang/article/details/1609720
什么是变长参数? 所谓含有变长参数的函数是指该函数可以接受可变数目的形参。例如我们都非常熟悉的 printf,scanf等等。 2:变长参数如何实现? 首先来看下面这样一个例子: #include<stdio.h> #include<stdarg.h> #include<string.h> void demo(char *msg,...) { va_list argp; int arg_number=0; char *para = msg; va_start(argp,msg); while(1) { if ( strcmp( para, "/0") != 0 ) { arg_number++; printf("parameter %d is: %s/n",arg_number,para); } else break; para = va_arg(argp,char *); } va_end(argp); } int main() { demo("Hello","World","/0"); system("pause"); return 0; } 实现这样一个函数要在内部使用va_list,va_start,va_arg,va_end,这些都是定义在 stdarg.h中的宏。 va_list是定义了一个保存函数参数的数据结构。 va_start(argp,msg)是将argp指向第一个可变参数,而msg是最后一个确定的参数。 最后一个确定的参数的含义是指它以后的参数都是可变参数,如果有下面的函数声明 void demo(char *msg1,char *msg2,...) 那么这里的最后一个确定参数就是msg2。 va_arg(argp,char *)返回当前参数的值,类型为char *,然后将argp指向下一个变长参 数。从这一步可以看出来我们可以通过va_start和va_arg遍历所有的变长参数。 va_end 将argp的值置为0。 下面我们看看上述几个宏在visual c++.net 2003 中的实现方法。首先是va_list的实现 #ifdef _M_ALPHA typedef struct { char *a0; /* pointer to first homed integer argument */ int offset; /* byte offset of next parameter */ } va_list; #else typedef char * va_list; #endif 可以看到va_list实际上是一个机器类型相关的宏,除了alpha机器以外,其他机器类 型都被定义为一个char类型的指针变量,之所以定义为char *是因为可以用该变量逐 地址也就是逐字节对参数进行遍历。 从上面可以看到,这些宏的实现都是和机器相关的,下面是大家常用的IX86机器下宏的 相关定义。 #elif defined(_M_IX86) #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define va_end(ap) ( ap = (va_list)0 ) #ifdef __cplusplus #define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) #else #define _ADDRESSOF(v) ( &(v) ) #endif 首先看_INTSIZEOF(n) 我们知道对于IX86,sizeof(int)一定是4的整数倍,所以~(sizeof(int) - 1) )的值一定是 右面[sizeof(n)-1]/2位为0,整个这个宏也就是保证了右面[sizeof(n)-1]/2位为0,其余位置 为1,所以_INTSIZEOF(n)的值只有可能是2,4,8,16,......等等,实际上是实现了字节对齐。 #define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) 所以va_start(ap,v)的作用就很明了了,_ADDRESSOF(v)定义了v的起始地址,_INTSIZEOF(v)定义了v所 占用的内存,所以ap 就指向v后面的参数的起始地址。 #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) ap += _INTSIZEOF(t) 使ap指向了后面一个参数的地址 而( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )相当于返回了目前t类型的参数的值。 #define va_end(ap) ( ap = (va_list)0 ) 将变量ap 的值置为0。 通过上述分析,再次印证了我么前面对可变参数实现的解释。 因此我们可以总结出变长参数函数的一般实现方法: 1:声明原型,形如void demo(char *msg,...),注意变长参数的原型声明中至少要含有 一个确定参数。 2:用va_list定义保存函数参数的数据结构,可以理解为一个指针变量(稍后会解释)。 3:用va_start将上一步定义的变量指向第一个可变参数。 4:用va_arg遍历所有的可变参数。 5:用va_end将指针变量持有的地址值置为0。