头文件: 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)—————————————————————————————————————————————————————————————————————————————