1. 基础知识
如果想要真正理解可变参数函数背后的运行机制,建议先理解两部分基础内容:
1)函数调用栈
2)函数调用约定
关于这两个基础知识点,我之前的笔记有详细介绍,感兴趣的童鞋可以移步这里:栈与函数调用惯例—上篇 和栈与函数调用惯例—下篇
2. 三个宏:va_start/va_arg/va_end
由man va_start可知,这簇宏定义在stdarg.h中,在我的测试机器上,该头文件路径为:/usr/lib/gcc/x86_64-redhat-linux/3.4.5/include/stdarg.h,在gcc源码中,其路径为:gcc/include/stdarg.h。
在stdarg.h中,宏定义的相关代码如下:
#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 #define va_copy(d,s) __builtin_va_copy(d,s) #endif #define __va_copy(d,s) __builtin_va_copy(d,s)其中,前3行就是我们所关心的va_start & var_arg & var_end的定义(至于va_copy,man中有所提及,但通常不会用到,想了解的同学可man查看之)。可见,gcc将它们定义为一组builtin函数。
// file: gcc/builtins.c /* The "standard" implementation of va_start: just assign `nextarg' to the variable. */ void std_expand_builtin_va_start (tree valist, rtx nextarg) { rtx va_r = expand_expr (valist, NULL_RTX, VOIDmode, EXPAND_WRITE); convert_move (va_r, nextarg, 0); // definition is in gcc/expr.c } // 上述代码中调用了expand_expr()来展开表达式,我猜测该函数调用完后,va_list指向了可变参数list前的最后一个已知类型参数 // file: gcc/expr.h /* Generate code for computing expression EXP. An rtx for the computed value is returned. The value is never null. In the case of a void EXP, const0_rtx is returned. */ static inline rtx expand_expr (tree exp, rtx target, enum machine_mode mode,enum expand_modifier modifier) { return expand_expr_real (exp, target, mode, modifier, NULL); }3. Windows系统VS内置编译器对va_start/va_arg/va_end的实现
/* file path: Microsoft Visual Studio 9.0\VC\include\stdarg.h */ #include <vadefs.h> #define va_start _crt_va_start #define va_arg _crt_va_arg #define va_end _crt_va_end可见,Windows系统下,仍然将va_start/va_arg/va_end定义为一组宏。他们对应的实现在vadefs.h中:
/* file path: Microsoft Visual Studio 9.0\VC\include\vadefs.h */ #ifdef __cplusplus #define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) #else #define _ADDRESSOF(v) ( &(v) ) #endif #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) #define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define _crt_va_end(ap) ( ap = (va_list)0 )备注:在VS2008提供的vadefs.h文件中,定义了若干组宏以支持不同的操作系统平台,上面摘出的代码片段是针对IA x86_32的实现。
4. 程序实例
经过上面对可变参数函数实现机制的分析,很容易实现一个带可变参数的函数。程序实例如下:
#include <stdio.h> #include <stdarg.h> void foo(char *fmt, ...) { va_list ap; int d; char c, *p, *s; va_start(ap, fmt); while (*fmt) { if('%' == *fmt) { switch(*(++fmt)) { case 's': /* string */ s = va_arg(ap, char *); printf("%s", s); break; case 'd': /* int */ d = va_arg(ap, int); printf("%d", d); break; case 'c': /* char */ /* need a cast here since va_arg only takes fully promoted types */ c = (char) va_arg(ap, int); printf("%c", c); break; default: c = *fmt; printf("%c", c); } // end of switch } else { c = *fmt; printf("%c", c); } ++fmt; } va_end(ap); } int main(int argc, char * argv[]) { foo("sdccds%%, string=%s, int=%d, char=%c\n", "hello world", 211, 'k'); return 0; }上面的代码很简单,旨在抛砖引玉,只要真正搞清楚了可变参数函数的原理,相信各位会写出更加复杂精细的可变参函数。
【参考资料】
1. linux man : va_start
2. wikipedia - x86 calling conventions
3. VS2008头文件:stdarg.h和vadefs.h的源码
================== EOF =================