我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()这个函数,这里将介绍可变函数的写法以及原理.
一般在调试打印Debug 信息的时候, 需要可变参数的宏. 从C99开始可以使编译器标准支持可变参数宏(variadic macros), 另外GCC 也支持可变参数宏, 但是两种在细节上可能存在区别.
1. __VA_ARGS__
__VA_ARGS__ 将"..." 传递给宏.如
#define debug(format, ...) fprintf(stderr, fmt, __VA_ARGS__)
在GCC中也支持这类表示, 但是在G++ 中不支持这个表示.
2. GCC 的复杂宏
GCC使用一种不同的语法从而可以使你可以给可变参数一个名字,如同其它参数一样。
#define debug(format, args...) fprintf (stderr, format, args)
这和上面举的那个定义的宏例子是完全一样的,但是这么写可读性更强并且更容易进行描述。
3. ##__VA_ARGS__
上面两个定义的宏, 如果出现debug("A Message") 的时候, 由于宏展开后有个多余的逗号, 所以将导致编译错误. 为了解决这个问题,CPP使用一个特殊的‘##’操作。
#define debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)
这里,如果可变参数被忽略或为空,‘##’操作将使预处理器(preprocessor)去除掉它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数,GNU CPP也会工作正常,它会把这些可变参数放到逗号的后面。
4. 其他方法
一种流行的技巧是用一个单独的用括弧括起来的的 "参数" 定义和调用宏, 参数在宏扩展的时候成为类似 printf() 那样的函数的整个参数列表。
#define DEBUG(args) (printf("DEBUG: "), printf(args))
写可变参数的C函数要在程序中用到以下这些宏:
void va_start( va_list arg_ptr, prev_param )
type va_arg( va_list arg_ptr, type )
void va_end( va_list arg_ptr )
va在这里是variable-argument(可变参数)的意思,这些宏定义在stdarg.h中.下面我们写一个简单的可变参数的函数,该函数至少有一个整数参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值.
void simple_va_fun(int i, ...) { va_list arg_ptr; int j=0; va_start(arg_ptr, i); j=va_arg(arg_ptr, int); va_end(arg_ptr); printf("%d %d\n", i, j); return; }
simple_va_fun(100); simple_va_fun(100,200);
va_start,va_arg,va_end是在stdarg.h中被定义成宏的,由于硬件平台的不同,编译器的不同,所以定义的宏也有所不同,下面以VC++中stdarg.h里x86平台的宏定义摘录如下:
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 )定义_INTSIZEOF(n)主要是为了内存对齐,C语言的函数是从右向左压入堆栈的(设数据进栈方向为从高地址向低地址发展,即首先压入的数据在高地址). 下图是函数的参数在堆栈中的分布位置:
对于可变参数的函数,因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数需要在该函数中由程序代码控制;另外,编译器对可变参数的函数的原型检查不够严格,对编程查错不利.
所以我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,无需用到可变参数.如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现.
#define debug(format, ...) fprintf(stderr, fmt, __VA_ARGS__) #define debug(format, args...) fprintf (stderr, format, args) #define debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)
void debug(const char *fmt, ...) { int nBuf; char szBuffer[1024]; va_list args; va_start(args, fmt); nBuf = vsprintf(szBuffer, fmt, args) ; assert(nBuf >= 0); printf("QDOGC ERROR:%s\n",szBuffer); va_end(args); }