c可变参数研究

们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,  
由于1)硬件平台的不同   2)编译器的不同,所以定义的宏也有所不同,下  
面以VC++中stdarg.h里x86平台的宏定义摘录如下(’/’号表示折行):  

typedef   char   *   va_list;  

#define   _INTSIZEOF(n)   /  
((sizeof(n)+sizeof(int)-1)&~(sizeof(int)   -   1)   )  

#define   va_start(ap,v)   (   ap   =   (va_list)&v   +   _INTSIZEOF(v)   )  

#define   va_arg(ap,t)   /  
(   *(t   *)((ap   +=   _INTSIZEOF(t))   -   _INTSIZEOF(t))   )  

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

定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函  
数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我  
们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再  
看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的  
地址,所以我们运行va_start(ap,   v)以后,ap指向第一个可变参数在堆  
栈的地址,如图:  

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

然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我  
们看一下va_arg取int型的返回值:  
j=   (   *(int*)((ap   +=   _INTSIZEOF(int))-_INTSIZEOF(int))   );  
首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回  
ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址  
(图2).然后用*取得这个地址的内容(参数值)赋给j.  

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

最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再  
指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不  
会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.  
在这里大家要注意一个问题:由于参数的地址用于va_start宏,所  
以参数不能声明为寄存器变量或作为函数或数组类型.  
关于va_start,   va_arg,   va_end的描述就是这些了,我们要注意的  
是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.  

(三)可变参数在编程中要注意的问题  

因为va_start,   va_arg,   va_end等定义成宏,所以它显得很愚蠢,  
可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能  
地识别不同参数的个数和类型.  
有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数  
printf是从固定参数format字符串来分析出参数的类型,再调用va_arg  
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通  
过在自己的程序里作判断来实现的.  
另外有一个问题,因为编译器对可变参数的函数的原型检查不够严  
格,对编程查错不利.如果simple_va_fun()改为:  
void   simple_va_fun(int   i,   ...)  
{  
va_list   arg_ptr;  
char   *s=NULL;  

va_start(arg_ptr,   i);  
s=va_arg(arg_ptr,   char*);  
va_end(arg_ptr);  
printf( "%d   %s/n ",   i,   s);  
return;  
}  
可变参数为char*型,当我们忘记用两个参数来调用该函数时,就会出现  
core   dump(Unix)   或者页面非法的错误(window平台).但也有可能不出  
错,但错误却是难以发现,不利于我们写出高质量的程序.  
以下提一下va系列宏的兼容性.  
System   V   Unix把va_start定义为只有一个参数的宏:  
va_start(va_list   arg_ptr);  
而ANSI   C则定义为:  
va_start(va_list   arg_ptr,   prev_param);  
如果我们要用system   V的定义,应该用vararg.h头文件中所定义的  
宏,ANSI   C的宏跟system   V的宏是不兼容的,我们一般都用ANSI   C,所以  
用ANSI   C的定义就够了,也便于程序的移植.    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值