可变参数函数(stdarg.h)

← C 语言知识地图 | ← 主页

依赖函数参数的栈布局,原理见 函数栈帧与压栈出栈


是什么

C 语言允许函数接受数量不定、类型可变的参数。printfscanfsprintf 都是可变参数函数。

通过 <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