您的位置:首页 > 编程语言 > C语言/C++

C语言函数的可变长度参数va_arg剖析

2014-09-18 15:46 417 查看
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


查看全文

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: