变长参数的实现
int printf(const char *format, ...);
那三个连续的点就代表大于或等于0个参数,再加上前面的format参数,所以printf函数至少要接受一个字符串,后面就随便了。但是这是如何实现的呢,不急,首先必须要了解C语言函数参数传递的机制。
C语言函数参数传递机制
我们知道,每个程序都有个用户栈,所有的函数调用都要使用它,比如被调用函数的实参及局部变量都要存在用户栈中,所以每个函数都有个栈帧。下面我用两个函数来说明这种调用机制:
int main(void) int add(int param1, int param2)
{ {
int a, b, c; int d;
a = 2; d = param1 + param2;
b = 3; return param1+param2;
c = add(a,b); }
return 0;
}
main函数在调用add函数时,main负责把b,a及eip(即返回地址)按顺序压栈,注意栈是向下增长的。然后执行call指令调用add函数。(ps: 其实在返回地址和局部变量d之间函数add还会压入一些寄存器,但是这对我们理解变长参数没有影响,总之要记住参数是自右向左压栈的)
处理变长参数的宏定义
处理变长参数说白了就是怎样获取那些在声明中没明说的参数,比如在printf("%d equals to %d.", a, b)中,第一个字符串参数可直接通过format取得,但a和b呢?这就要需要对参数传递的理解了。主函数调用printf时肯定是先压入b,再压入a,最后压入"%d equals to %d"的地址。
现在我们来获取a的地址,很简单,当然是 (char*)&format+sizeof(const char*)。其中&format是参数format在栈中的地址,而a正好format上面。
OK, 现在我们可以系统地讲解处理变长参数的宏了。
在头文件stdarg.h中先定义个类型 va_list,它是专门用来去可变参数的,如果要对付可变参数首先要定义一个va_list类型的变量ap:
typedef char* va_list;
因为在获取参数地址时必须知道它前一个参数的数据类型大小,所以要定义__va_sizeof(type),它的作用和sizeof一样,不过__va_sizeof(char),__va_sizeof(short)的值都是4:
#define __va_sizeof(type) (((sizeof(type) + sizeof(int) - 1) / sizeof(int)) * sizeof(int))
然后定义va_start获取第一个可变参数的地址:
#define va_start(ap, last) ((ap) = (va_list)&(last) + __va_size(last))
va_arg用于获取ap当前指向的参数的值,并顺带把va_list移向下一个参数
#define va_arg(ap, type) (*(type *)((ap) += __va_size(type), (ap) - __va_size(type)))
当用完ap时,用va_end将其赋值为空,也就是0
#define va_end(ap) (ap)=0
处理变长参数的一般框架
如果我们自己实现printf函数,代码的形式大概如下
int chparamf(const char *format, ...)
{
va_list ap;
va_start(ap,format);
循环使用 va_arg(ap,你要取的参数的数据类型)
va_end(ap);
}
posted on 2009-11-08 13:51 John Waken 阅读(1388) 评论(0) 编辑 收藏 举报