printf、scanf 等函數能接受不定數量參數,靠的是C標準庫提供的 <stdarg.h> 機制。本文從使用示例切入,深入剖析 va_list、va_start、va_arg、va_end 的底層實現原理(基於Linux內核源碼),揭開可變參數的神秘面紗。
myprintf 實現#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
void myprintf(char *format, ...) {
va_list ap;
va_start(ap, format); // 初始化參數列表
int pos = 0;
char buf[64];
while (format[pos] != '\0') {
if (format[pos] == '%') {
pos++;
switch (format[pos]) {
case 'd': case 'u': {
int val = va_arg(ap, int); // 取出 int 參數
sprintf(buf, "%d", val);
write(STDOUT_FILENO, buf, strlen(buf));
break;
}
case 'f': {
double val = va_arg(ap, double); // 取出 double(float會提升為double)
sprintf(buf, "%f", val);
write(STDOUT_FILENO, buf, strlen(buf));
break;
}
default: break;
}
} else {
write(STDOUT_FILENO, &format[pos], 1); // 普通字元直接輸出
}
pos++;
}
va_end(ap); // 清理
}
int main() {
myprintf("this is a testing, i = %d, u = %u, f = %f", -1, 5, 0.2);
// 輸出: this is a testing, i = -1, u = 5, f = 0.200000
return 0;
}
acenv.h)
#ifndef _VALIST
#define _VALIST
typedef char *va_list; // va_list 本質是 char* 指標
#endif
// 對齊邊界計算
#define _AUPBND (sizeof(acpi_native_int) - 1) // 向上對齊掩碼
#define _ADNBND (sizeof(acpi_native_int) - 1) // 向下對齊掩碼
// 計算型別 T 對齊後的大小
#define _bnd(X, bnd) (((sizeof(X)) + (bnd)) & (~(bnd)))
// 核心四大宏
#define va_arg(ap, T) (*(T *)(((ap) += _bnd(T, _AUPBND)) - _bnd(T, _ADNBND)))
#define va_end(ap) (void)0
#define va_start(ap, A) (void)((ap) = (((char *)&(A)) + _bnd(A, _AUPBND)))
va_list:參數列表游標char *,指向棧上參數區的當前位置。va_start(ap, A):定位到第一個可變參數A 是最後一個固定參數(如 format)。&A 取其地址 → 棧上固定參數區末尾。_bnd(A, _AUPBND) 計算 A 對齊後大小 → 偏移量。ap = (char *)&A + 對齊大小,指向第一個可變參數起始位置。va_arg(ap, T):取出當前參數並推進游標ap += _bnd(T, _AUPBND):先將游標向前移動 型別 T 對齊後的大小(指向下一個參數)。... - _bnd(T, _ADNBND):再減去對齊大小,回到 當前參數的起始地址。*(T *):按型別 T 解引用,取得值。ap 已更新為下一個參數位置。va_end(ap):清理(void)0(空操作)。| 陷阱 | 說明 | 對策 |
|---|---|---|
| float 傳遞 | 可變參數中 float 自動提升為 double | va_arg(ap, double) 取值,再轉 float |
| 型別不匹配 | 調用端傳 int,va_arg 取 long → 未定義行為 | 格式字串嚴格對應型別,編譯器 -Wformat 檢查 |
| 參數個數未知 | 無內建機制獲取實際參數數量 | 格式字串隱含個數(如 printf),或約定哨兵值 |
| 引用/類對象 | C++ 非 POD 型別不可作可變參數 | 僅用於 C 相容函數或 POD 型別 |
可變參數機制的核心是棧上參數的線性佈局 + 對齊規則 + 指標算術。va_start 定位起點,va_arg 按型別大小與對齊逐個「踩著」參數走過。理解這一原理,不僅能正確使用 <stdarg.h>,更能在調試崩潰、分析棧幀、編寫序列化庫時遊刃有餘。