C语言可变参函数浅析

来源:互联网 发布:大学生python兼职 编辑:程序博客网 时间:2024/06/06 04:33

虽然学习C语言很久了,但实际上一直没有去仔细看C语言中中的可变参函数是如何实现的,最常见的一个就是printf函数,最近闲暇时间看了下,对其有了初步的了解。

首先需要了解下C语言的传递参数机制,C语言调用函数时,参数保存在栈里的,并且栈的增长方向是从低地址到高地址的,(栈是一种数据结构,其最显著的特点就是“先进后出”)并且是从后往前扫描的。具体来说就是最后面参数最先入栈,最前面的参数最后入栈,同时会第一个出栈。

栈的地址,如图:


高地址
       |-----------------------------| 
       |函数返回地址               |
       |-----------------------------| 
       |.......                                | 
       |-----------------------------| 
       |第n个参数(第一个可变参数) | 
       |-----------------------------|
       |第n-1个参数(最后一个固定参数)  |
       |-----------------------------|

       |......  第一个参数 ..........|


低地址 


C语言的可变参主要通过三个定义的macro来实现,在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 );


其中va_list是自定义的数据类型,一般为char *, 也有的系统定义为void *,要实现可变参数,就必须按照要求使用这3个macro。

va_start定义如下:

  1. #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )   
  2. #define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )   //得到可变参数中第一个参数的首地址  
 定义_INTSIZEOF(n)主要是为了处理字节对齐的问题。

void va-start(va-list ap,lastfix)是一个宏,它使va-list类型变量ap指向被传递给函数的可变参数表中的第一个参数,在第一次调用va-arg和va-end之前,必须首先调用该宏。va-start的第二个参数lastfix是传递给被调用函数的最后一个固定参数的标识符。va-start使ap只指向lastfix之外的可变参数表中的第一个参数,很明显它先得到第一个参数内存地址,然后又加上这个参数的内存大小,就是下个参数的内存地址了。

#define va_arg(ap,type)    ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )    //将参数转换成需要的类型,并使ap指向下一个参数  

type va-arg(va-list ap,type)也是一个宏,其使用有双重目的,第一个是返回ap所指对象的值,第二个是修改参数指针ap使其增加以指向表中下一个参数。va-arg的第二个参数提供了修改参数指针所必需的信息。在第一次使用va-arg时,它返回可变参数表中的第一个参数,后续的调用都返回表中的下一个参数

#define va_end(ap)      ( ap = (va_list)0 )  

 va-end必须在va-arg读完所有参数后再调用,否则会产生意想不到的后果。特别地,当可变参数表函数在程序执行过程中不止一次被调用时,在函数体每次处理完可变参数表之后必须调用一次va-end,以保证正确地恢复栈。
       一个变参数函数至少需要有一个普通参数,其普通参数可以具有任何类型。

例如我们要实现一个简单的sum函数,可接受可变数目参数,第一个参数为后面可变参数的数目,假定后面的可变参数均为int型,就需要按下面的步骤:

1.函数原型  int sum(int n, ...)

2.声明一个va_list类型的变量ap并用va_start初始化

va_list ap;

va_start(ap, n);

3.利用va_arg(ap,int)循环获取下一个参数的值

4.调用va_end(ap)进行终止。

代码如下:

  1. #include<iostream>   
  2. using namespace std;  
  3. #include<stdarg.h>   
  4.   
  5. int sum(int n,...)  
  6. {  
  7.     int i , sum = 0;  
  8.     va_list vap;  
  9.     va_start(vap , n);     //指向可变参数表中的第一个参数   
  10.     for(i = 0 ; i < n ; ++i)  
  11.         sum += va_arg(vap , int);     //取出可变参数表中的参数,并修改参数指针vap使其增加以指向表中下一个参数   
  12.     va_end(vap);    //把指针vap赋值为0   
  13.     return sum;  
  14. }  
  15. int main(void)  
  16. {  
  17.     int m = sum(3 , 45 , 89 , 72);  
  18.     cout<<m<<endl;  
  19.     return 0;  

当然与printf相比,这个过于简单,主要是参数的数目和类型都固定了,printf主要是扫描通过前面的格式化字符串从而确定可变参的数目和类型的,有时间了可以自己尝试写下printf。



原创粉丝点击