在 C 語言開發中,不定參數(Variadic Functions)是實現像 printf、scanf 這類函數的關鍵機制。本文將通過實例、標準庫宏、手動實現以及底層宏定義四個維度,系統講解不定參數的工作原理與使用技巧。
最直觀的理解方式是觀察參數在棧上的分佈。下面的示例通過指針運算直接訪問棧上的參數:
#include <stdio.h>
/* 函數參數以棧形式存取,從右至左入棧 */
void fun(int a, ...) {
int *temp = &a;
temp++; // 指向第一個可變參數
int i;
for (i = 0; i < a; ++i) {
printf("%d\n", *temp);
temp++;
}
}
int main() {
int a = 1, b = 2, c = 3, d = 4;
fun(4, a, b, c, d); // 輸出 1 2 3 4
return 0;
}
注意:此方法強依賴編譯器棧布局,屬於非標準寫法,僅供原理演示。
<stdarg.h> 規範用法ANSI C 提供了 va_list、va_start、va_arg、va_end 四個宏,實現可移植的不定參數處理:
va_start(ap, last_fixed):初始化 ap 指向第一個可選參數va_arg(ap, type):取得當前參數並推進到下一個va_end(ap):清理 ap示例:打印任意數量的字符串,以空字符串結束
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
int demo(char *msg, ...) {
va_list argp;
int argno = 0;
char *para;
va_start(argp, msg);
while (1) {
para = va_arg(argp, char*);
if (strcmp(para, "") == 0) break;
printf("Parameter #%d is: %s\n", argno, para);
argno++;
}
va_end(argp);
return 0;
}
int main(void) {
demo("DEMO", "This", "is", "a", "demo!", "");
return 0;
}
printf不依賴 stdarg.h,直接操作棧內存模擬格式化輸出:
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
void myprintf(char *fmt, ...) {
char *pArg = NULL;
char c;
pArg = (char *)&fmt;
pArg += sizeof(fmt); // 跳過 fmt 指針本身
do {
c = *fmt;
if (c != '%') {
putchar(c);
} else {
switch (*++fmt) {
case 'd':
printf("%d", *((int *)pArg));
break;
}
pArg += sizeof(int);
}
++fmt;
} while (*fmt != '\0');
pArg = NULL;
}
int main(void) {
myprintf("the fist : %d\nthe secound : %d\n", 1, 2);
return 0;
}
stdarg.h 宏定義底層詳解以 VC++6.0 x86 平台為例,宏定義揭示了棧指針運算的本質:
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define va_start(ap, v) (ap = (va_list)&v + _INTSIZEOF(v))
#define va_arg(ap, t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define va_end(ap) (ap = (va_list)0)
_INTSIZEOF 保證參數按 int 大小對齊va_start 利用最後一個固定參數地址計算第一個可變參數地址va_arg 先推進指針再回溯取值va_end 將指針置空#include <iostream>
#include <stdarg.h>
const int N = 5;
using namespace std;
void Stdarg(int a1, ...) {
va_list argp;
int i, ary[N];
va_start(argp, a1);
ary[0] = a1;
for (i = 1; i < N; i++)
ary[i] = va_arg(argp, int);
va_end(argp);
for (i = 0; i < N; i++)
cout << ary[i] << endl;
}
void main() {
Stdarg(5, 12, 64, 34, 23);
}
不定參數的核心在於棧幀布局與指針運算。實際開發中應優先使用 <stdarg.h> 保證可移植性;手動操作棧僅適合教學或極受限環境。掌握其原理有助於理解調用約定、調試棧損壞等高階問題。