可变参数的函数,即参数个数可变、参数类型不定的函数。设计一个参数个数可变、参数类型不定的函数是可能的,最常见的例子是printf函数、scanf函数和高级语言的Format函数。在C/C++中,为了通知编译器函数的参数个数和类型可变(即是不定的、未知的),就必须以三个点结束该函数的声明
- //printf函数的声明
- int printf(const char * _Format, ...);
- //scanf函数声明
- int scanf(const char * _Format, ...);
在C/C++中,任何使用变长参数声明的函数都必须至少有一个指定的参数(又称强制参数),即至少有一个参数的类型是已知的,而不能用三个点省略所有参数的指定,且已知的指定参数必须声明在函数最左端。
变长参数函数的实现
含有变长参数的函数是怎么实现的呢?变长参数函数的实现其实关键在于怎么使用参数,指定了的参数好说,直接使用指定的参数名称访问,但未指定的参数呢?我们知道函数调用过程中参数传递是通过栈来实现的,一般调用都是从右至左的顺序压参数入栈,因此参数与参数之间是相邻的,知道前一个参数的类型及地址,根据后一个参数的类型就可以获取后一个参数的内容。对于可变参数函数,结合一定的条件,我们可以通过栈顶参数获取之后的省略参数内容。如,对于函数printf,我们知道了参数_Format的地址及类型,就可知道第一个可变参数的栈地址(如果有的话),如果知道第一个可变参数的类型,就可知道第一个可变参数的内容和第二个可变参数的地址(如果有的话)。以此类推,可以实现对可变参数函数的所有参数的访问。
那么,要怎么指定上诉的“一定的条件”呢?最简单的方法就像printf等函数一样,使用格式化占位符。分析格式化字符串参数,通过事先定义好的格式化占位符可知可变参数的类型及个数,从而获取各个参数内容。一般对于可变参数类型相同的函数也可直接在强制参数中指定可变参数的个数和类型,这样也能获取各个参数的内容。无论哪种,都涉及对栈地址偏移的操作。结合栈存储模式和系统数据类型的字长,我们根据可变参数的类型很容易可以得到栈地址的偏移量。
在C++语言的#include <stdarg.h>头文件中提供了三个函数va_start, va_end,va_arg和一个类型va_list。利用它们,我们可以很容易实现一个可变参数的函数。首先简单介绍一下这三个函数。
假设现在有一个名为f的函数,其函数定义为:
void f(int a, int b, ...)
那么,在函数的内部,为了获得这些可变参数,就需要利用到va_start、va_arg和va_end三个宏。
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, type) ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )
#define va_end(ap) ( ap = (va_list)0 )
va_list类型的变量可以用于存储可变类型的变量,用它可以对可变变量进行遍历;
va_list ap
在使用ap之前,必须调用va_start使得ap和可变参数进行关联;
va_start(ap, b);
va_start的第二个参数是函数参数列表中最后一个非可变参数的参数;
然后就可以调用va_arg对参数进行访问了;
而且,在可变长参数中,需要注意字节对齐。也就是float类型被扩展成double;char, short被扩展成int。因此,如果你要去可变长参数列表中原来为float类型的参数,需要用va_arg(argp, double)。对char和short类型的则用va_arg(argp, int)。
type va_arg( ap, type);
在变量处理完成之后,程序结束之前,应该调用va_end一次;
void va_end(ap);
-
下面是printf的函数实现
- void my_printf(const char *format,...)
- {
- va_list ap;
- va_start(ap,format); //将ap指向第一个实际参数的地址
- while(*format)
- {
- if(*format != '%')
- {
- putchar(*format);
- format++;
- }
- else
- {
- format++;
- switch(*format)
- {
- case 'c':
- {
- char valch = va_arg(ap,int); //记录当前实践参数所在地址
- printch(valch);
- format++;
- break;
- }
- case 'd':
- {
- int valint = va_arg(ap,int);
- printint(valint);
- format++;
- break;
- }
- case 's':
- {
- char *valstr = va_arg(ap,char *);
- printstr(valstr);
- format++;
- break;
- }
- case 'f':
- {
- float valflt = va_arg(ap,double);
- printfloat(valflt);
- format++;
- break;
- }
- default:
- {
- printch(*format);
- format++;
- }
- }
- }
- }
- va_end(ap);
- }