对C可变参数宏的一种实现的个人剖析


头文件: stdarg.h

va_ 系列包括4个宏程序和一个类型定义 :(其中 va_copy 在有些版本不存在,有的有__va_copy )

类型定义va_list
宏程序va_start
va_args
va_copy
    / __va_copy
va_end

—————————————————————————————————————————————————————————————————————————————

GCC源码:

#define va_start(v,l)	__builtin_va_start(v,l)
#define va_end(v)	__builtin_va_end(v)
#define va_arg(v,l)	__builtin_va_arg(v,l)
#if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L || defined(__GXX_EXPERIMENTAL_CXX0X__)
#define va_copy(d,s)	__builtin_va_copy(d,s)
#endif
#define __va_copy(d,s)	__builtin_va_copy(d,s)
显然 GCC 完全依靠内置函数调用 实现了这几个宏。

—————————————————————————————————————————————————————————————————————————————

一种具体点的实现源码:


类型 va_list :

#if defined(__svr4__) || defined(_AIX) || defined(_M_UNIX) || defined(__NetBSD__)
typedef char *__gnuc_va_list;                                                             
#else
typedef void *__gnuc_va_list;
#endif
typedef __gnuc_va_list va_list;

可以看到 va_list 实际上就是char *或者void * 的一个指针。

—————————————————————————————————————————————————————————————————————————————

宏程序va_start

#define va_start(AP, LASTARG) 						\
 (AP = ((__gnuc_va_list) __builtin_next_arg (LASTARG)))

尝试以下测试代码:

#include <stdio.h>
#include <stdarg.h>

void func(int x1,...)
{
    printf("parameter x1's address is   :%p \n",&x1);
    va_list va;
    va = __builtin_next_arg(x1);
    printf("the parameter next to x1 is :%p\n",va);
}

int main()
{
    func(1,2,3);
    return 0;
}
运行结果:

parameter x1's address is   :0xbfc9c940
the parameter next to x1 is :0xbfc9c944

函数调用 的时候,参数从后往前(从右向左)压栈,后面(右侧)的参数在高址。

可见, __builtin_next_arg(LASTARG)函数旨在获取紧挨在LASTARG之后的一个参数在栈帧占用空间的低址。

va_start(AP,LASTARG) 宏便是将挨着参数LASTARG的下一个参数所占用空间的低地址赋值给AP

注意: 这里参数所占用空间的低地址并不是我故意 在绕口,此时AP并不一定是下一个参数的起始地址。

假设有下面的接口:

void function(char param1,int param2)

1. 在 栈帧中,char  param1 究竟占几个bit是取决于系统和编译器的,它有可能 占sizeof(char)空间 ,

也有可能占sizeof(short) 空间,也有可能是sizeof(int)  空间,我用的GCC就是sizeof(int),4bit空间。

2. 假设char参数占用4bit空间 :地址:00000000 - 00000004,(当然这么低的地址实际上是不可能

的) 那么真正的参数位置也有两种可能 :

     00000000 - 00000001  大端机器 ( little-endian machine )

或者

     00000003 - 00000004  小端机器 ( big-endian machine )

而这点区别正好可以在接下来的va_arg中体现:

—————————————————————————————————————————————————————————————————————————————

宏程序 va_arg

#if (defined (__arm__) && ! defined (__ARMEB__)) || defined (__i386__) 
     || defined (__i860__) || defined (__ns32000__) || defined (__vax__)
/* This is for little-endian machines; small args are padded upward.  */
#define va_arg(AP, TYPE)						\
 (AP = (__gnuc_va_list) ((char *) (AP) + __va_rounded_size (TYPE)),	\
  *((TYPE *) (void *) ((char *) (AP) - __va_rounded_size (TYPE))))
#else /* big-endian */
/* This is for big-endian machines; small args are padded downward.  */
#define va_arg(AP, TYPE)						\
 (AP = (__gnuc_va_list) ((char *) (AP) + __va_rounded_size (TYPE)),	\
  *((TYPE *) (void *) ((char *) (AP)					\
		       - ((sizeof (TYPE) < __va_rounded_size (char)	\
			   ? sizeof (TYPE) : __va_rounded_size (TYPE))))))
#endif /* big-endian */
va_arg  展开后其实是两个表达式被 ‘ , ’隔开组成的大表达式:

(AP = (__gnuc_va_list) ((char *) (AP) + __va_rounded_size (TYPE)),	\
*((TYPE *) (void *) ((char *) (AP) - __va_rounded_size (TYPE))))

第一个表达式将AP这个指针向移动到下一个参数占用空间的地址。

第二个表达式 计算刚刚走过的这个参数的值。

由于逗号运算符的构成 的表达式的值是最后一项的值,所以 当我们写:i = va_arg( ...)  的 时候,i 被赋值第二个表达式的值。

这里宏__va_round_size(TYPE) 是用来计算TYPE 类型在作为参数传递的时候,在 栈帧上占有空间大小的。定义如下 :

#if defined(sysV68)
#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (short) - 1) / sizeof (short)) * sizeof (short))
#elif defined(_AIX)
#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (long) - 1) / sizeof (long)) * sizeof (long))
#else
#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))
#endif
—————————————————————————————————————————————————————————————————————————————

宏程序 va_end__va_copy

在这种实现中,va_list 只是一个指针,没有什么好收尾的, 拷贝也是直接复制就可以 。

#define __va_copy(dest, src) (dest) = (src)
#undef va_end
void va_end (__gnuc_va_list);        /* Defined in libgcc.a */
#define va_end(AP)    ((void)0)
—————————————————————————————————————————————————————————————————————————————

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值