printf(const char *fmt, …)是一个可变参数函数, 第一个参数为字符串, 后面是格式化输出参数列表. C语言中函数的参数都是压进栈中的(参数压栈方向是从右往左!), 可变参数的函数必须有一个参数表示参数的个数, 才能让编译器知道压栈多少参数, 以及函数返回时弹出多少个参数, 我们在fmt字符串中提取’%‘的个数, 以及针对’%'后面不同的字符来处理。
1.va_list类型
va_list 的 va 是variable-argument(可变参数). list 就是列表的意思
typedef char *va_list; //Linux内核中va_list的实现
va_list ap;
2.va_start
va_start(ap, A)宏将ap初始化,以便随后被va_arg()和va_end()使用,必须先调用。
#define va_start(ap,fmt) ( ap = (va_list)&fmt + _INTSIZEOF(fmt) )
第一个参数ap, 就是刚刚提到的va_list类型, 第二个参数A, 就是一个确定的类型, 也就是"…"的类型, 他会将这些类型的地址放到va_list中, 根据初始化A来初始化AP.
注意: 传给宏va_start 的参数fmt是可变参数列表中的前一个参数, ap指向变参列表中的第一个参数地址.
函数参数压栈时, 参数的入栈顺序时从右往左, 出栈时是从左往右. 函数调用时, 先把若干个参数都压入栈中, 再压fmt, 最后压pc, 这样一来, 栈顶指针偏移找到了fmt, 通过fmt中的%占位符, 取后面参数的个数, 从而正确获得所有参数.
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
计算int类型按4字节对齐后的结果. 通过_INTSIZEOF(n), 可以根据一个变量的类型计算变量再内存中占用的字节数, 从而正确定位参数在内存的位置.
3.va_arg
va_arg(ap, T)宏扩展为一个表达式, 该表达式具有调用中下一个参数的类型和值.
va_arg()宏在va_start()宏之后的第一次使用会返回最后的参数. 连续的调用会返回其余参数的值.
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADN BND))))// Linux 内核中宏定义原型
#define va_arg(ap,type) ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )
4.va_end
va_end 是一个宏
#define va_end(ap) (ap = (va_list) NULL)
#define va_end(ap) ( ap = (va_list)0 )
通过这个宏, 可以清楚的看到, 将va_list类型的ap赋值为NULL.
示例:
void test(int fmt, ...)
{
va_list ptr;//相当于char* ptr
va_start(ptr, fmt);//
int par1 = va_arg(ptr, int);//par1 = 15
char par2 = va_arg(ptr, char); //par2 = 'E'
double para3 = va_arg(ptr, double); //para3 = 3.1415920000000002
float para4 = va_arg(ptr, float); //para4=2.00000000
va_end(ptr);//本质是ptr = null
}
int main()
{
int ret = 1;
int para1 = 15;
char para2 = 'E';
double para3 = 3.141592;
float para4 = 3.1415926f;
test(1,para1,para2, para3, para4);
printf("hello world");
return 0;
}
5.vsprintf(char *buf, const char *fmt, va_list args) 函数, 将变量列表args中的参数按照fmt中规定的格式保存到临时缓冲buf中.
如果成功,则返回写入的字符总数,否则返回一个负数。
#include <stdio.h>
#include <stdarg.h>
char buffer[80];
int vspfunc(char *format, ...)
{
va_list aptr;
int ret;
va_start(aptr, format);
ret = vsprintf(buffer, format, aptr);
va_end(aptr);
return(ret);
}
int main()
{
int i = 5;
float f = 27.0;
char str[50] = "runoob.com";
vspfunc("%d %f %s", i, f, str);
printf("%s\n", buffer);
return(0);
}