C语言的printf函数,可以输入不同的参数,一般通过函数重载的方式实现这种函数名相同而参数不同的机制,但考虑到printf的所有可能性,函数重载很明显解决不了这个问题,printf 和vsprintf 使用了可变长度参数来实现这种机制。
先看一个典型作用
void f(int flag, ...){
va_list args;
va_start(args, flag);
int n = va_arg(args, int);
}
C语言的函数调用机制
C语言的函数调用机制,是将函数参数与函数调用后下一条指令的地址都压入栈中,然后跳到函数的入口地址。
例如
void func(int param1, double param2,int param3){ }
int main(){
func(3, 1.2, 4);
printf("Over\n"); //设指令地址为0x1234
return 0;
}
执行f(3, 1.2, 4)的函数调用,进入func函数时的堆栈如下:
这样,通过param1的地址就可以计算出param2与param3的地址:
¶m2 = ((char*)¶m1) + sizeof(param2)
所以...只是对后面的参数的一种省略的写法,只要我们在程序中进行相应的计算与类型转换,就可以得到每个参数的值。
模拟实现
//方法一
void func(int param1, double param2, int param3){
double *p2 = (double*) ((char*) param1) + sizeof(param1);
int *p3 = (int*) ((char*)param1)+sizeof(param1)+sizeof(param2);
}
//方法二
void func(int param1, double param2, int param3){
char * p;
p = (char*)¶m1 + sizeof(param1);//参数一的结尾
p+= sizeof(double);//参数二的结尾
double *p2 = (double*) (p - sizeof(double);
p+=sizeof(int);//参数三的结尾
int *p3 = (int*) (p-sizeof(int);
}
这样就可以猜测va_list va_start va_arg va_end的实现
va_list 声明一个指针变量,跟踪当前参数的指针;
va_start 初始化这个指针的位置,最开始指向第一个参数的结尾的位置
va_arg 则移动指针,指向一个新参数的位置,并返回当前值
void func(int param1, double param2, int param3){
va_list p;
va_start(p, param1);
double p2 = va_arg(double);
double p3 = va_arg(int);
}
再和方法二的实现进行对照
可以预测
va_list <-> char *
va_start <-> p=&beginparam
va_arg <-> x=*p, p+=sizeof(x)
va_end做了什么?
Linux的实现方法
查看Linux0.11的内核源代码,对va_list, va_start, va_arg 的实现如下:
1. va_list的实现没有差别,char*
typedef char *va_list;
2. va_start的实现
#define va_start(AP, LASTARG) \
(__builtin_saveregs (), &n
先看一个典型作用
void f(int flag, ...){
va_list args;
va_start(args, flag);
int n = va_arg(args, int);
}
C语言的函数调用机制
C语言的函数调用机制,是将函数参数与函数调用后下一条指令的地址都压入栈中,然后跳到函数的入口地址。

void func(int param1, double param2,int param3){ }
int main(){
func(3, 1.2, 4);
printf("Over\n"); //设指令地址为0x1234
return 0;
}
执行f(3, 1.2, 4)的函数调用,进入func函数时的堆栈如下:

¶m2 = ((char*)¶m1) + sizeof(param2)
所以...只是对后面的参数的一种省略的写法,只要我们在程序中进行相应的计算与类型转换,就可以得到每个参数的值。
模拟实现
//方法一
void func(int param1, double param2, int param3){
double *p2 = (double*) ((char*) param1) + sizeof(param1);
int *p3 = (int*) ((char*)param1)+sizeof(param1)+sizeof(param2);
}
//方法二
void func(int param1, double param2, int param3){
char * p;
p = (char*)¶m1 + sizeof(param1);//参数一的结尾
p+= sizeof(double);//参数二的结尾
double *p2 = (double*) (p - sizeof(double);
p+=sizeof(int);//参数三的结尾
int *p3 = (int*) (p-sizeof(int);
}
这样就可以猜测va_list va_start va_arg va_end的实现
va_list 声明一个指针变量,跟踪当前参数的指针;
va_start 初始化这个指针的位置,最开始指向第一个参数的结尾的位置
va_arg 则移动指针,指向一个新参数的位置,并返回当前值
void func(int param1, double param2, int param3){
va_list p;
va_start(p, param1);
double p2 = va_arg(double);
double p3 = va_arg(int);
}
再和方法二的实现进行对照
可以预测
va_list <-> char *
va_start <-> p=&beginparam
va_arg <-> x=*p, p+=sizeof(x)
va_end做了什么?
Linux的实现方法
查看Linux0.11的内核源代码,对va_list, va_start, va_arg 的实现如下:
1. va_list的实现没有差别,char*
typedef char *va_list;
2. va_start的实现
#define va_start(AP, LASTARG) \
(__builtin_saveregs (), &n