C语言 1000问 · 可变参数Q1:怎么实现一个可变参数函数?va_list / va_start / va_arg / va_end 各自做什么?

来源:Flomo 原始笔记|查看原笔记|迁移日期:2026-10-31

C语言 1000问 · 可变参数Q1:怎么实现一个可变参数函数?va_list / va_start / va_arg / va_end 各自做什么?

▎ 五大学习法:西蒙学习法——把「可变参数」钻透

可变参数是什么

  • 函数参数个数不固定,如 printf(fmt, …)、execl、ioctl。
  • 用省略号 … 声明:int sum(int count, …);
  • 关键难点:函数怎么知道「有几个参数、各是什么类型」——C 本身不告诉你,得靠约定(printf 靠格式串,或调用者显式传个数)。

四个宏的分工(都在 stdarg.h)

  • va_list ap; ——声明一个「参数游标」,记录当前读到哪。
  • va_start(ap, last); ——初始化游标,指向「最后一个具名参数」之后的第一个可变参数。last 必须是最后一个固定参数。
  • va_arg(ap, type); ——取出一个参数(按 type 解释),并把游标移到下一个。
  • va_end(ap); ——收尾清理(一般与 va_start 配对,某些平台需要它做栈清理)。

一个例子(求和)

  • int sum(int n, …) { va_list ap; va_start(ap, n); int s = 0; for (int i = 0; i < n; i++) s += va_arg(ap, int); va_end(ap); return s; }
  • 调用 sum(3, 1, 2, 3) → 6。个数 n 由调用者保证,函数自己数不出来。

关键限制(都要记牢)

  1. 无法自动知道参数个数和类型:必须靠调用约定(printf 靠格式串,或显式传 n),类型错了就是未定义行为。
  2. va_arg 的类型必须和实参「提升后」一致:char / short 会被提升成 int,float 提升成 double——取的时候要用 int / double,不能用 char / float。
  3. 不能中途知道还要取几个:循环终止条件要靠显式信息(n、结束标记如 NULL、格式串解析)。
  4. 不能直接把可变参数转发给另一个可变参数函数:要用 vprintf / vsnprintf 这类「v 开头」的版本(接收 va_list)。
  5. va_list 用完必须 va_end;不要重复 va_start 同一个 list,或跨函数用同一个 list。

典型用途

  • printf / scanf 家族、日志封装(LOG(fmt, …) 转发给 vfprintf)。
  • 参数个数不定的系统调用(ioctl、execl)。
  • 自定义格式化、错误信息拼接。
  • 嵌入式日志:封装可变参数 + vsnprintf 输出到串口 / 缓冲。

和「固定参数 + 数组」比

  • 可变参数灵活但危险(类型不安全、无个数检查)。
  • C 里若参数同类型、个数不定,用「数组 + 长度」更安全。

一句话:C 的可变参数靠 va_list 游标 + va_start 定位、va_arg 逐个取、va_end 收尾;它不告诉你个数和类型,全靠调用约定——所以最容易出的 bug 是类型不匹配和个数数错,这俩在 C 里都是未定义行为。

📎 问题库(专栏⑭导航)→ https://v.flomoapp.com/mine/?memo_id=MjQ1NzM0NTk5

📎 文件I/O Q4(文件定位)→ https://v.flomoapp.com/mine/?memo_id=MjU2NDUwNTY4

C语言