揭秘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语言的方式来实现

 

 

 

 揭秘C可变参数原理_第1张图片

你可能感兴趣的:(C++,C)