你是否碰到过形如
void log(char *fmt, ...)
{
..........
}
的函数呢?
如果没有的话可以man 3 printf
#include <stdio.h> int printf(const char *format, ...);
|
不错,大家最常见的printf就是这样的一个例子,当无法列出传递给函数的所有实参的类型和数目时,可用省略号指定参数表
那么这个...到底是怎么实现的呢?
我们先看这几个宏:
#include
void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
这几个宏都要用到一个va_list类型的变量,这个变量的定义也在里
------------------va_start()-------------------
这个宏的原型如下:
void va_start(va_list ap, last);
这个宏主要的作用就在于定义一个va_list类型的变量,供后边的va_arg和va_end使用,所以这个宏得最先被调用.
这里的参数last指的是最后一个有确定类型的变量的名字,通常指的就是...之前的那个变量的名字,而ap则是...所代表的参数集.
如void log(char *fmt, a,b,c),此时last就应该是fmt,而宏调用完后ap就应该是a,b,c了
因为va_start宏可能会用到last的地址,所以last不应该是寄存器变量,函数或数组
-------------------va_arg()-------------------
这个宏的原型如下:
type va_arg(va_list ap, type);
这里的参数ap就是va_start定义的那个ap,每调用一次va_arg,就会修改ap的值,使其向前移动一个参数,这样的话,下一次调用va_arg就能返回下一个值
参数type指的是你希望ap中的当前变量以什么类型来获得.
沿用上边va_start的例子:va_arg(ap,char*);此时ap是a,b,c 而你希望以字符串的形式读入a,所以type写成char*,当这个宏调用完毕后,va_arg就是b,c了,而下一次调用va_arg返回的就是b,如果你希望以int来读取b,那就应该va_arg(ap,int),再下一次就是c
如果ap中没有下一个参数或者下一个参数的类型和你希望的类型type不一致,那就会出现随机的error
-------------------va_end()-------------------
这个宏的原型如下:
void va_end(va_list ap);
在大多数C语言上,调用va_end与否并无区别。但是,某些版本的va_start宏为了方便对va_list进行遍历,就给参数列表动态分配内存,这样当调用va_end以后ap这个变量就会释放,所以如果忘记调用va_end,可能会引起内存泄露。
下面写一个简单的例子,来实现简单的printf函数:
#include <stdio.h> #include <stdarg.h>
void foo(char *fmt, ...) { va_list ap; int d; char c, *s;
va_start(ap, fmt); while (*fmt) switch (*fmt++) { case 's': /* string */ s = va_arg(ap, char *); printf("%s\n", s); break; case 'd': /* int */ d = va_arg(ap, int); printf("%d\n", 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\n", c); break; } va_end(ap); }
int main() { foo("%s%d%c","inet",11,'a'); return 0; }
|
其实ANSI C标准要求,并且很多C语言实现也提供的vprintf,vfprintf,vsprintf系列函数就是来实现这一功能的,这些函数与对应的printf函数族中的函数在行为方式上完全一样,只不过用va_list代替了可变参数列表。所以实现printf函数可以这样实现:
#include <stdio.h> #include <stdarg.h>
void foo(char *fmt, ...) { va_list ap;
va_start(ap, fmt);
vprintf(fmt,ap); va_end(ap); }
int main() { foo("%s%d%c","inet",11,'a'); return 0; }
|
变长参数列表主要用于配置文件、选项、参数的格式化分析,也多用于项目中的log
或错误处理,当项目中随着程序规模的增大,程序员经常感觉到有必要进行系统化的错误处理,所以就很自然的可以利用这个方法来实现如下形式的错误处理:
erro_log("%s is illagle",str);
reference:
man va_start
《c陷阱与缺陷》
阅读(882) | 评论(0) | 转发(0) |