什么是可变参数
在C语言编程中有时会遇到一些参数可变的函数、例如printf()、scanf(),其函数原型为:
int printf(const char *format,...)
int scanf(const char *format,...)
它除了有一个参数format固定以外,后面的参数其个数和类型都是可变的,用三个点"..."作为参数占位符号。
参数列表的构成
任何一个可变参数都可以分为两个部分:固定参数和可选参数。至少有一个固定参数,其声明与普通函数声明相同;可选参数由于数目不定(0个或以上),声明时必须用"..."表示。固定参数和可选参数共同构成的可变参数函数的参数列表。
实现原理
C语言中使用va_list系列变参宏实现变参函数,此处va意为variable-argument(可变参数)。
x86平台VC6.0集成开发环境中,stdarg.h头文件内变参宏定义如下:
typedef char *va_list;
// 把n 圆整到sizeof(int)的倍数
#define _INTSIZEOF(n)((sizeof(n)+sizeof(int)-1)& ~(sizeof(int)-1))
//初始化 ap指针,使其指向第一个可变参数。v是变参列表的前一个参数
#define va_start(ap,v) (ap = (va_list)&v + _INTSIZEOF(v))
//该宏返回当前变参值,并使ap指向列表中的下一个变参
#define va_arg(ap,type) (*(type *)((ap +=_INTSIZEOF(type) - _INTSIZEOF(type))))
//将指针ap置为无效,结束变参的获取
#define va_end(ap) (ap = (va_list)0)
_INTSIZEOF(n)****
_INTSIZEOF宏考虑到某些系统需要内存地址对齐。从宏名看应按照sizeof(int)即栈粒度对齐,参数在内存中的地址均为sizeof(int) = 4的倍数。
例如,若1<= sizeof(n)<=4,则_INTSIZEOF(n) = 4;
若5<=sizeof(n)<=8,则_INTSIZEOF(n) = 8。
va_start(ap,v)
va_start宏首先根据(va_list)&v得到参数v在栈中的内存地址,加上_INTSIZEOF(v)所占内存大小后,使ap指向v的下一个参数。在使用的时候,一般用这个宏初始化ap指针,v是变参列表的前一个参数,即最后一个固定参数,初始化的结果是ap指向第一个变参。
va_arg(ap,type)
这个宏取得type类型的可变参数值。首先ap +=_INTSIZEOF(type),即ap跳过当前可变参数而指向下个变参的地址;然后ap-_INTSIZEOF(type)
得到当前变参的内存地址,类型转换后解引用,最后返回当前变参值。
自己实现一个可变参函数
#include <stdio.h>
//模拟可变参数函数,用于计算多个整数的和
int sum(int count, int first, ...)
{
int total = first;
//创建指向第一个可变参数的指针
int* p = &first;
//遍历所有的参数
for (size_t i = 1; i < count; i++)
{
p++;//移动到下一个参数位置
total += *p;//解引用并添加
}
return total;
}
int main()
{
int result = sum(5,1,2,3,4,5);
printf("sum::%d\r\n", result);
return 0;
}
可变参应注意的事项
- 确保第一个参数指定可变参数的数量和类型
- 使用正确的类型读取参数
- 可变参数的类型一致性
- 可变参数不能是数组和结构体
- 可变参函数不进行类型检查
- 小心浮点类型的默认提升
- 避免在循环中使用可变参数
- 使用标准库宏来避免手动解析