可变参数函数(stdarg.h)
依赖函数参数的栈布局,原理见 函数栈帧与压栈出栈
是什么
C 语言允许函数接受数量不定、类型可变的参数。printf、scanf、sprintf 都是可变参数函数。
通过 <stdarg.h> 提供的四个宏实现:
| 宏 | 作用 |
|---|---|
va_list | 定义一个指针变量,用于遍历可变参数 |
va_start(ap, last_fixed) | 初始化指针,指向第一个可变参数 |
va_arg(ap, type) | 取出当前指向的参数,指针后移到下一个 |
va_end(ap) | 清理,置空指针 |
最小示例
#include <stdarg.h>
#include <stdio.h>
void my_printf(const char *fmt, ...) {//注意这里的三个点是可变参数函数必须加的
va_list args;
va_start(args, fmt); // 初始化,指向 fmt 之后第一个可变参数
int n = va_arg(args, int); // 取出一个 int,指针后移
printf("n = %d\n", n);
va_end(args); // 清理
}
int main(void) {
my_printf("ignore", 42);
// 输出: n = 42
}原理:参数从右到左压栈
C 语言函数参数从右到左依次压入栈中:
printf("a=%d, b=%d", 10, 20);压栈顺序:先压 20,再压 10,最后压 "a=%d, b=%d"。栈布局(满减栈,地址从高到低):
高地址
"a=%d, b=%d"(fmt) ← 最后一个压入,固定参数,地址已知
10 ← 倒数第二个压入
20 ← 第一个压入
低地址 ← SP
va_start(args, fmt) 做的事:拿到 fmt 的地址,往后(向高地址方向,即”下一个参数”的方向)偏移一个 fmt 类型的大小,就定位到了第一个可变参数 10 的位置。
va_arg(args, int) 做的事:从当前位置读取一个 int(4 字节),指针后移 4 字节,指向下一个参数 20。
va_end(args) 做的事:把 args 置为 NULL,防止野指针。
va_start: fmt 地址 + sizeof(fmt) → 指向 10
va_arg: 读 10 → 指针 += sizeof(int) → 指向 20
va_arg: 读 20 → 指针 += sizeof(int) → 指向末尾
va_end: args = NULL