揭秘C可变参数原理

2015/12/05 - C/C++

Demo

// 宏定义在stdarg.h,用来确定可变参数列表中每个参数的内存地址
void va_start( va_list arg_ptr, prev_param); 
type va_arg( va_list arg_ptr, type ); 
void va_end( va_list arg_ptr );
void simple_va_fun(int i,...) { 
           va_list arg_ptr; 
           int j=0; 
           va_start(arg_ptr, i); 
           j=va_arg(arg_ptr, int); 
           va_end(arg_ptr); 
           printf("%d %d\n", i, j); 
           return; 
}
//在程序中可以这样调用: 
simple_va_fun(100); 
simple_va_fun(100,200);

从这个函数的实现可以看到,我们使用可变参数有以下步骤:

1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.。

2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.。

3)然后用va_arg返回可变的参数,并赋值给整数j。va_arg的第二个参数是你要返回的参数的类型,这里是int型。

4)最后用va_end宏结束可变参数的获取。如果函数有多个可变参数的,依次调用va_arg获取各个参数.

可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型。

在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如

⑴在固定参数中设标志– printf函数就是用这个办法。

⑵在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾

原理

//VC++ x86平台
typedef char * va_list; 

// 子节对齐
#define _INTSIZEOF(n) \ 
((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) 

//运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) 


//首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址(图2).然后用*取得这个地址的内容(参数值)赋给j.
#define va_arg(ap,t) \ 
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 

#define va_end(ap) ( ap = (va_list)0 )
    高地址|-----------------------------| 
    |函数返回地址 | 
    |-----------------------------| 
    |.......| 
    |-----------------------------|  <--va_arg后ap指向 
    |第n个参数(第一个可变参数)| 
    |-----------------------------|  <--va_start后ap指向 
    |第n-1个参数(最后一个固定参数)| 
    低地址|-----------------------------|  <-- &v 

实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定 详情点击

①函数栈的生长方向

②参数的入栈顺序

③CPU的对齐方式

④内存地址的表达方式

va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。

如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现


如果文章对您有帮助,欢迎扫描下方二维码赞助(一分也是爱噢),谢谢

Search

    一分也是爱噢 一分也是爱

    目录