函数参数分类
- 基本类型参数:int、float、char等,传递时传递值拷贝,函数内部修改不会影响外部原变量
- 复杂类型参数:数组、结构体、指针
○ 数组:传参时自动退化为指针,函数内拿到的是数组首地址
○ 结构体:可以传值(拷贝整个结构体),也可以传结构体指针(推荐,节省拷贝开销)
基本类型参数定义时必须写类型,void func(int a),不能省略类型
可变参数(不定量参数)基础
- 关键字:... 省略号,表示可变参数列表
- 头文件:<stdarg.h>,提供va_list、va_start、va_arg、va_end这一组宏
- 语法规则:可变参数...必须放在参数列表最后面,前面至少要有 1 个固定参数
c
int Sum(int count, ...);// ✅合法
int Sum(...); // ❌错误,...不能放前面
四大可变参数宏
| 宏 | 作用 |
|---|---|
va_list ap; |
定义一个可变参数迭代器变量 ap,本质是指针 |
va_start(ap, 固定参数名) |
初始化 ap,让 ap 指向第一个可变参数;必须指定最后一个固定参数 |
va_arg(ap,类型) |
取出当前 ap 指向的参数,并且 ap 自动移动到下一个参数;必须指定正确类型 |
va_end(ap) |
清理 ap,结束可变参数读取,防止内存问题 |
c
#include <stdio.h>
#include <stdarg.h>
// count:传入参数个数,后面是可变参数
int Sum(int count, ...)
{
va_list ap;
va_start(ap, count); // ap指向第一个可变参数
int res = 0;
for(int i = 0; i < count; i++)
{
res += va_arg(ap, int); // 取出int类型参数
}
va_end(ap); // 收尾
return res;
}
int main(void)
{
printf("%d\n", Sum(3,10,20,30)); // 3个参数:10+20+30
return 0;
}
printf 原理
c
int printf(const char *format, ...);
- format是固定参数(格式化字符串),后面...接收任意数量、任意类型参数
- 通过解析format字符串里的%d %f %s,判断后面可变参数的类型,再调用va_arg取出对应参数
⚠️坑:格式符和实际参数类型不匹配,会直接得到乱码(未定义行为),va_arg不会自动识别类型,你写什么类型它就取多少字节
底层 & 反汇编视角
va_start 的底层:拿到栈上可变参数区域的起始地址,ap 本质就是栈指针
- 可变参数本质:调用方把所有参数按调用约定放到寄存器 / 栈上;被调用函数靠 va 系列宏,手动遍历这片内存
- 没有任何机制帮你记录:可变参数一共有多少个、每个是什么类型!必须靠固定参数传递信息(printf 靠字符串标记、Sum 靠 count)
- 调试:断点进可变参数函数,查看ap的值,内存窗口跟着 ap 查看栈上参数;反汇编能看到 va_start/va_arg 展开后的内存读写指令
可变参数常见坑
- 不能自动识别参数数量、类型,类型写错直接读错内存
- va_arg类型不能写char/short,可变参数会默认整型提升,char 会被提升为 int
- va_start、va_end必须成对使用
- ...只能放在参数末尾