原文来自:http://www.dutor.net/index.php/2011/08/variadic/
使用变参
C/C++提供了函数的可变参数(variadic)机制,大部分人写的第一个C程序恐怕就是Hello World吧,使用的应该也是printf(“Hello, World\n”)。printf就是一个使用可变参数的典型,它的原型声明为,
int printf(const char *fmt, ...); |
其中返回值为实际输出字符个数,fmt为格式控制字符串,而”…”便声明了一个可变参数,你可以根据传递0个或多个参数给printf。printf内部会根据格式控制串中的格式指定符号(d, f, p等等)来逐个解析通过可变参数传进的实参变量。
为解析可变参数,C语言提供了一个va_list类型和四个宏,分别是va_start, va_arg, va_end, 和va_copy,这些宏声明在stdarg.h中。为了方便描述,下面实现一个简单的类似printf的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | void mockprintf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); for (const char *s = fmt; *s; ++s) { switch(*s) { case 'd': printf("meet d\n"); int d = va_arg(ap, int); printf("%d\n", d); break; case 's': printf("meet s\n"); const char *str = va_arg(ap, char*); printf("%s\n", str); break; case 'c': printf("meet c\n"); char c = va_arg(ap, int); printf("%c\n", c); break; default: printf("unknown format specifier\n"); } } va_end(ap); } int main() { mockprintf("cdfs", 'A', 0x45, "string"); return 0; } |
va_list的实现与编译器和平台相关,通常是一个指向参数栈的指针。va_start使用变参列表前的最后一个命名参数(named argument)作为参数,以此定位变参列表的第一个参数的地址,并将ap指向该参数(此处假设va_list实现为指针)。宏va_arg需要两个参数,va_list变量和下一个预期的参数的类型,该宏以指定类型返回(展开为)对应的参数值,并调整va_list指向下一个参数。最后,每一个va_start需要一个va_end作为结束。另外,示例函数中没有用到va_copy,这是一个用来复制va_list变量到另一个va_list变量的宏,目的是应对平台间va_list实现的差异。
值得一提的是,va_start和va_end可以重复调用,用以多次对变参列表进行解析。
printf家族
C的printf家族包含8个成员,原型如下,
1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h> int printf(const char *fmt, ...); int fprintf(FILE *stream, const char *fmt, ...); int sprintf(char *str, const char *fmt, ...); int snprintf(char *str, size_t size, const char *fmt, ...); #include <stdarg.h> int vprintf(const char *fmt, va_list ap); int vfprintf(FILE *stream, const char *fmt, va_list ap); int vsprintf(char *str, const char *fmt, va_list ap); int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); |
前四个函数没有什么特殊的。后面四个v系列可以接受va_list变量,通常用在对可变参数输出的包装,在日志记录系统中较为常用。比如下面代码,
1 2 3 4 5 6 7 8 9 | |
v系列函数并不会调用va_end宏,因此在这些函数返回后,需要调用函数自己进行va_end。若要再次解析变参列表,就需要重新va_start, va_end。
宏变参
除了函数,在C/C++中,带参宏定义也可以接受变参,使用方法和函数类似。比如,若将上面的log函数的某个级别的日志输入定义成宏,
#define log_warn(fmt, ...) log(WARN, fmt, __VA_ARGS__) |
__VA_ARGS__只是被预处理器简单的展开为传递给宏log_warn的变参列表,包括逗号分隔符。若想使用具有鲜明意义的名字,而不是统一的__VA_ARGS__,可以这样,
#define log_warn(fmt, args...) log(WARN, fmt, args) |
上述宏定义中,有一个问题值得注意,就是当变参列表为空时,log函数调用的参数列表会有一个结尾的逗号,这在某些编译器中会被诊断为错误(据说MSVC不会),这种情况下可以将fmt也纳入变参列表,
#define log_warn(...) log(WARN, __VA_ARGS__) |
hi 猴哥,
Solaris上装wireshark的问题解决了,wireshark不能在Solaris上安装,Solaris上只能安装wireshark的父亲版本:Ethereal,但是仍需要安装三个库:gtk+、glib、libcap,具体方法见如下网址:http://wenku.baidu.com/view/1a037108bb68a98270fefa04.html
你是哪个啊,学弟?
为什么不能安装呢?
提到可变参数就不得不提到压栈方式 ,这篇文可没讲啊
对于可变参数的成员函数,始终使用__cdecl的压栈方式
如果你之前用typedef一个函数类型,然后用__stdcall压栈方式就会有问题的,因为两者压栈方向是相反的,而且stdcall是完毕后是由被调用方清理的
如:
typedef char* (_stdcall SERVERFUNC)(void *, void *, int);
typedef SERVERFUNC * PSERVERFUNC;// 定义函数类型(这个是函数指针)
谢谢补充。
考虑到如果考虑调用约定,展开讨论就过于庞大了,就偷懒了,(^_^)
话说我不清楚 __cdecl、__stdcall等这些啊。
能给讲讲不?这些都是什么,有什么用啊?
嗯,这个东西确实值得了解,哪天总结一下发上来吧。