C可变参数函数-3(转)

 

上面式子+号的左边都可从上一个式子得到,关键是右边要求进行计算。每个参数都可以由其类型来决定大小,再结合每个参数必定占用大小为4的倍数的栈,因此可用如下公式来计算:occupy_stack(type) = (sizeof(type) + sizeof(int) - 1) & (~sizeof(int))

依此根据上面的公式则,stdarg.h中几个宏的原理就跃然于纸上了。

va_list 类型

va_list是一个隐式类型,意味开发人员不必细研它的具体类型,只要使用va_list类型来出现就不会出错。va_list类型是用于记录可变参数的地址。

va_start(va_list ap, last)

last为最后一个命名参数,va_start宏使ap记录下第一个可变参数的地址,原理与可变参数1的地址 = 参数a的地址 + a占用栈的大小相同。从ap记录的内存地址开始,认为参数的数据类型为type并把它的值读出来;把ap记录的地址指向下一个参数,即ap记录的地址 += occupy_stack(type)

va_arg(va_lit ap, type)

这里是获得可变参数的值,具体工作是:从ap所指向的栈内存中读取类型为type的参数,并让ap根据type的大小记录它的下一个可变参数地址,便于再次使用va_arg宏。从ap记录的内存地址开始,认为存的数据类型为type并把它的值读出来;把ap记录的地址指向下一个参数,即ap记录的地址 += occupy_stack(type)

va_end(va_list ap)

用于释放”ap变量,它与va_start对称使用。在同一个函数内有va_start必须有va_end

 

谈到这里,大家都对上面的三个宏和va_list有清楚的认识。下面是VC++6.0编译器对va_list和三个宏在Intel CPU下的实现。

  

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 )

 

VC++的实现中,把va_list定义为char *类型,这样va_list就是一个地址,它可以指向4G线性空间的任间一个地址。 _INTSIZEOF(n)与上面分析到的occupy_stack是完全一回事,目的也相同。va_start(ap, v)与上面分析的一样,使ap指向第一个可变参数的地址。va_arg(ap, t)恐怕最难理解,它要返回当前参数的值,并且要指向下一个可能参数。ap += _INTSIZEOF(t))是先使ap指向下一个参数,然后用((ap - _INTSIZEOF(t)) 得到当前可变参数的地址,再用*(t *)来得到该参数的值。va_end(ap)的定义为大家提供了一种思想, bool类型的0值为false,指针不使用的时用NULL来标记,而在这里,ap不用就用(char *)0来表示。如果va_end后,没有va_start来初始化,直接用va_arg肯定会出现的。这样保证了标准的要求:ap必须要用va_start进行初始化。

VC++的实现中,考虑了参数大小和数据对齐问题,使得可变参数的类型不但可以是基本类型,同样适用于用户定义类型。值的注意的是,如果是用户定义类型,最好用typedef定义的名字作为类型名,这样就会减少在va_arg进行宏展开时出错的机率。

 

可变参数函数相关话题

         va_start, va_argva_endC可变参数函数的三剑客,这一事实在ANSIC标准已确立了。ANSIC99标准在此基础上提供了一个新的宏va_copy,它用于拷贝两个va_list变量,定义如下:

          void va_copy(va_list dst, va_list src);

         va_copy别无它意,只希望程序具有更高的可移植性和安全性,因此在拷贝va_list变量时,必须使用va_copy宏进行,否则在不同的实现上会产生不可预知结果,甚至出错。和va_start相同,由va_copy初始化的va_list在使用结束时必须使用va_end释放

        除了不同的实现引起它相应的va_copy不同之外,还有问题也值得去关注:如何把可变参数传递给下一个可变参数函数。

       在可变参数函数中,由va_list变量来记录(或获得)可变参数部分,但是va_list中并没有记录下它们的名字,事实上也是不可能的。要想把可变参数部分传递给下一个函数,唯有通过va_list变量去传递,而可原来定义的函数用"..."来表示可变参数部分,而不是用va_list来表示。为了方便程序的标准化,ANSIC在标准库代码中就作出了很好的榜样:在任何形如: type fun( type arg1, type arg2, ...)的函数,都同时定义一个与它功能完成一样的函数,但用va_list类型来替换"...",即type fun(type arg1, type arg2, va_list ap)。以printf函数为例:

int printf(const char *format, ...);     

int vprintf(const char *format, va_list ap);

第一个函数用"..."表示可变参数,第二个用va_list类型表示可变参数,目的是用于被其它可变参数调用,两者在功能功能上是完全上一样。只是在函数名字相差一个'"v"字母。写一个专门用于调试输出的函数,那就非常方便了:

int debug_log(const char * fmt, ...)

{

va_list ap;

va_start(ap, fmt);

          int n = vpintf(fmt, ap);

          va_end(ap);

 

          return n;

}

  值得注意的是,在实现vprintf这类函数时,在函数里面不能对传递进来的va_list调用va_end,并且在函数调用完后va_list变量的值同样是不可知的,应该马上调用va_end;如果想在被调用函数返回后继续使用,应先用va_copy拷贝一份再传给函数。下面就是一个很好的例子:

int debug_log(const char * fmt, ...)

{

va_list ap;

va_start(ap, fmt);

         va_list another;

           va_copy(another, ap);

          int n = vpintf(fmt, another);

          va_end(another);

         ... //继续使用ap

         va_end(ap);

          return n;

}

 

小结

可变参数函数作为C语言的特性之一,在C99标准增加宏va_copy,足以见它还是被受关注的。它的实现原理因CPU架构而异,本文主要介绍在Intel32CPU上的实现;其它的CPU实现原理与此类似,但背后的机制与原理却是相同的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值