前言
在可变参数函数中,经常可以看到va_list、va_start、va_arg、va_end
的使用。
可变参数函数是什么?
C语言中,printf
函数,就是一个可变参数函数, 传入的参数数量是不确定的,可以传入多个。
printf函数原型:
int printf(const char *format, ...)
其中 ...
,代表的就是可变参数,类似于省略号。
示例
下面通过一个简单的例子来体验可变参数函数。
static void va_list_test(int param_num, ...)
{
int i = 0;
va_list ap;
va_start(ap, param_num);
// 获取整型参数
int arg1 = va_arg(ap, int);
printf("arg1 = 0x%x\n", arg1);
// 获取字符串
char *arg2 = va_arg(ap, char *);
printf("arg2 = %s\n", arg2);
// 获取整型参数
int arg3 = va_arg(ap, int);
printf("arg3 = 0x%x\n", arg3);
va_end(ap); // 注意需要 va_end(ap)
}
int main()
{
va_list_test(2, 0x55, "arg test", 0x66);
return 0;
}
输出结果如下:
arg1 = 0x55
arg2 = arg test
arg3 = 0x66
分析
上述例子,分别传入了4个参数, 2, 0x55, "arg test", 0x66
, 可以从 va_list_test()函数传入参数的格式来看:
参数 param_num 的作用
为何需要param_num
的固定参数?
- 参数param_num, 即上述例子传入的
数字2
, 是固定参数。在上述例子无实际使用,用于给va_start
定位可变参数的位置。va_start(ap, param_num)
可以理解为,通过固定参数param_num
来找到可变参数存储的起始位置, 并保存到了ap
变量中。这个与函数调用过程,函数帧入栈有关系,不展开讨论。
分别获取参数的过程
获取第一个可变参数
- 参数0x55, 是整型参数, 由于
va_start(ap, param_num)
通过传入的参数param_num
定位到了可变参数的起始位置, 所以第一条获取整型参数 0x55, 用va_arg(ap, int)
来获取第一个可变参数,0x55, 根据ap
的位置,读取一个整型数值。
获取第二个可变参数
- 参数"arg test", 是字符串参数, 由于读取了0x55后, ap的位置已经改变,变成了第二个参数的起始位置, 所以
va_arg(ap, char *)
会以当前位置,获取一个字符串数值,所以得到"arg test"
。
获取第三个可变参数
- 最后一个参数 0x66, 也是同理, 继续
va_arg(ap, int)
获取一个int 参数。
小总结
- 需要理解第一个固定参数的作用, 即
va_start(ap, param_num)
, 同时需要关注ap
这个参数在获取可变参数中的意义, 可以理解为指针地址便宜,始终指向未获取的第一个可变参数地址 - 实验过程,尝试将可变参数1、2、3的获取顺序改变, 即获取顺序由
int 、 char *、int
修改为int 、int、char *
, 会出现,参数2 获取异常, 参数3 printf后出现段错误。 所以变量的获取顺序不能出错,否则会有异常。 va_start()
与va_end()
需要配对出现。 不使用va_end(ap)
的后果,猜测可能是会导致内存泄漏。 可以自行查询。
va_list的常用方式
在大部分的实际情况下,va_list都是与printf()函数的使用类似。
如以下例子
int va_list_test(char *fmt, ...)
{
char out[1024] = {0};
va_list ap;
va_start(ap, fmt);
vsnprintf(out, sizeof(out), fmt, ap);
printf("%s", out);
va_end(ap);
}
int main()
{
va_list_test("output : 0x%x, %s, 0x%x\n", 0x55, "arg test", 0x66);
return 0;
}
输出结果如下:
output : 0x55, arg test, 0x66
分析
在上述例子中, 固定参数fmt
实际就是传入的 "output : 0x%x, %s, 0x%x\n"
, 三个可变参数,分别按照 %x、%s、%x的形式,通过vsnprintf()
格式化输入到out中。类似sprintf()
。
需要注意的是为什么用的vsnprintf()
先看下vsnprintf
函数的原型, 可以看到最后传入的一个参数,就是 va_list 变量,所以使用 vsnprintf
在上述例子中非常便捷。
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
小总结
- 注意vsnprintf()函数的使用方法。
- va_list常用情况经验积累。