1 2 3 4 5
| typedef char *va_list; //va_list就是个普通的指向char类型的指针而已 #define _INTSIZEOF(n) ( (sizeof(n)+sizeof(int)-1)& ~(sizeof(int)-1) ) //这是获取某个类型的长度,并按照int长度为边界对齐,不足4bytes补足,因为栈宽度为4bytes #define va_start(ap,v) (ap=(va_list)&v + _INTSIZEOF(v)) //这就是使ap指向v的以int长度为边界的后一个位置 #define va_arg(ap, type) ( *(type *)((ap+=_INTSIZEOF(type) )-_INTSIZEOF(type)) ) //这其实就是,先使 ap+=_INTSIZEOF(type),跳过下一个4字节对齐了的type长度(改变了ap本身的值),然后再返回 ap-_INTSIZEOF(type)位置处的值,是个type类型 #define va_end(ap) (ap=(va_list)0)
|
1 2 3 4 5 6 7 8
| void printf(char *fmt, … ) //参数会按照从右向左的顺序依次入栈 { va_list va; va_start(va, fmt); //根据fmt在栈上的地址,获取到 … 那一串参数的开始地址,即validate va的值 va_arg(va); //以后每调用一次va_arg(va),就获取了一个…的值,至于获取多少个是个头,fmt含有…参数个数的信息(要么直接给出,要么隐含有,比如字符串里%的个数)
va_end(va); }
|
约定:如果函数参数是带有…的,那就va_start一套组合拳获取参数值。如果传进来的是一个va_list的值,就省略va_start操作,因为va_start 就是validate va的值,人家都给你传进来了,你还多此一举干嘛。经过分析,一个…函数没有办法再次调用…函数,因为没有办法通过va_start正确找到 … 的开始位置!
除非使用宏,如下:
1 2 3 4 5
| #define fb (fmt, …) fa(fmt, ##__VA_ARGS__) //##__VA_ARGS__就只是告诉编译器,fa可以接受可变参数,不要报错,相当于又把…传递下去了(两个#的解释请参见宋宝华《linux设备驱动程序开发详解 4.0内核》p80) Void fa(fmt, …) { xxxxx }
|
问题:一个含有…(可变参数)的函数,可不可以将…对应的参数全部传递给一个新的含…(可变参数)的函数?(即一个…函数可以调用另一个…函数吗?)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void fa(char *fmt, …) { va_list va; va_start(va,fmt); //va_arg(va); //fb(fmt,va)
va_end(va); }
void fb(char *fmt, …) { va_list v; va_start(v,fmt); //va_arg(v);
va_end(v); }
int main() { int i=10; fa("hello fang,%d%s\n",i,"ding"); }
|
按照…的约定,fb()内部取参数的时候还是使用va_start(va,fmt)来初始化va(而不是使用传进来的va初始化它。fmt和va确实都指向了有效的地方,但是fb其实是想根据fmt这个指针所占内存的地址、即栈上的位置来拿参数的),所以第二次调用va_arg后,其实拿到的东西就不属于本函数栈里的内容了,即越界了!
但是Joey说可以,将参数格式化输出到buffer再将buffer传递下去。vsprintf()