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