ddddfang's Blog.

printf-可变参数

字数统计: 707阅读时长: 2 min
2019/01/30 Share
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);
}

img01
约定:如果函数参数是带有…的,那就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");
}

img01

按照…的约定,fb()内部取参数的时候还是使用va_start(va,fmt)来初始化va(而不是使用传进来的va初始化它。fmt和va确实都指向了有效的地方,但是fb其实是想根据fmt这个指针所占内存的地址、即栈上的位置来拿参数的),所以第二次调用va_arg后,其实拿到的东西就不属于本函数栈里的内容了,即越界了!
但是Joey说可以,将参数格式化输出到buffer再将buffer传递下去。vsprintf()

CATALOG