即参数个数、类型皆不定的函数,最常见的如printf()函数;
早期Unix System V兼容方式头文件名是<varargs.h>, ANSI标准规范指定头文件名<stdarg.h>, GCC目前已经不再支持 include varargs.h文件
va_list(), va_arg(), va_start(), va_end();
<linux kernel version 3.0> printk() 源码(删减):
asmlinkage int printk(const char *fmt, ...) { va_list args; int r; va_start(args, fmt); r = vprintk(fmt, args); va_end(args); return r; }其中, ... 表示不定参数。大致流程为:
(1). 声明 va_list 变量 args,其中,va_list 类型为 char *;
typedef char *va_list;(2). 调用va_starts(args, fmt)函数,获取fmt参数后面的不定参数中的第一个参数地址;va_start(args, fmt);(3). 调用子函数vprintk()(4). 调用函数va_end(args)使得arg指向NULL
va_start 的宏定义如下:
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))其中,_bnd宏定义为:
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))_bnd(X,bnd)的功能是返回X类型按照当前机器对齐后占用空间的大小:按四字节对齐时,_AUPBND的值是3,若X类型的大小小于4,占用空间大小为4,若大于4,按照进位成4的整数倍返回
函数调用时,参数入栈的顺序是从右往左,而栈空间地址是向下延伸的,所以从最左边参数到最右边参数是一块地址连续增长的内存。因此,va_start宏的作用是,使得指针参数ap指向固定参数(char * fmt)后的第一个参数。
指向第一个参数后,可以调用宏va_arg()来获取下一个参数地址,定义如下:
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))首先将ap的地址指向下一个参数地址,然后表达式返回第一个参数的值。
va_end宏定义很简单:
#define va_end(ap) (void) 0在此版本定义中va_end不作任何操作,一般认为需要将ap指针指向NULL:#define va_end(ap) ap=(char*)0
这样在代码中就消除了“野指针”。
功能: 实现一个求若干个数的平方和的函数,至少有一个参数,参数结束标志为一个-1的参数。标志位存在的原因是,变参函数并不能确定何时停止读入函数。
函数代码如下:
int sum_square(int n,...) { int k = 0; int temp =0; va_list args; if(n == -1){ return 0; } va_start(args,n); k+= n*n; while((temp=va_arg(args,int)) != -1){ k+= temp * temp; } va_end(args); return k; }
若函数调用没有-1参数,则会出现难以预料的结果,一般是由于访问非法内存产生的“段错误”。
printf("sum of square is %d \n",sum_square(1,2,3,4,-1));运行后显示:sum of square is 30