C 程式設計/stdarg.h
stdarg.h 是一個頭檔案,允許函式接受不定數量的引數。[1] 它提供了遍歷未知數量和型別函式引數列表的功能。
stdarg.h 的內容通常用於可變引數函式,但也可以在可變引數函式呼叫的其他函式(例如 vprintf)中使用。
可變引數函式是可以接受可變數量引數的函式,它們在最後一個引數的位置使用省略號 (...) 宣告。printf 就是這樣一個函式的例子。一個典型的宣告是
int check(int a, double b, ...);
可變引數函式必須至少有一個命名引數,因此例如
char *wrong(...);
在 C 中是不允許的。(在 C++ 中,這種宣告是允許的,但並不實用。)在 C 中,逗號必須放在省略號之前;在 C++ 中,逗號是可選的。
定義中使用相同的語法
long func(char, double, int, ...);
long func(char a, double b, int c, ...)
{
/* ... */
}
省略號也可以出現在舊式函式定義中
long func();
long func(a, b, c, ...)
char a;
double b;
{
/* ... */
}
| 名稱 | 描述 | 相容性 |
|---|---|---|
va_list |
用於迭代引數的型別 | C89 |
| 名稱 | 描述 | 相容性 |
|---|---|---|
va_start |
使用 va_list 開始迭代引數 |
C89 |
va_arg |
檢索引數 | C89 |
va_end |
釋放 va_list |
C89 |
va_copy |
將一個 va_list 的內容複製到另一個 va_list |
C99 |
要訪問未命名的引數,必須在可變引數函式中宣告一個型別為 va_list 的變數。然後使用 va_start 宏呼叫它,它有兩個引數:第一個是宣告為 va_list 型別的變數,第二個是函式最後一個命名引數的名稱。在此之後,每次呼叫 va_arg 宏都會產生下一個引數。va_arg 的第一個引數是 va_list,第二個引數是傳遞給函式的下一個引數的型別。最後,在函式返回之前必須對 va_list 呼叫 va_end 宏。(不需要讀取所有引數。)
C99 提供了一個額外的宏 va_copy,它可以複製 va_list 的狀態。宏呼叫 va_copy(va2, va1) 將 va1 複製到 va2 中。
沒有定義用於確定傳遞給函式的未命名引數的數量或型別的機制。函式只需要以某種方式知道或確定這一點,具體方法各不相同。常見的約定包括
- 使用
printf或scanf樣式的格式字串,其中包含指示引數型別的嵌入式說明符。 - 可變引數結尾處的哨兵值。
- 一個表示可變引數數量的計數引數。
一些 C 實現提供 C 擴充套件,允許編譯器檢查格式字串和哨兵的正確使用。在沒有這些擴充套件的情況下,編譯器通常無法檢查傳遞的未命名引數是否與函式期望的型別匹配,或者將它們轉換為所需的型別。因此,應注意確保這方面的正確性,因為如果型別不匹配,將導致未定義的行為。
例如,如果呼叫 va_arg(ap, short *),則必須在呼叫中將該引數的空指標傳遞為 (short *)0;使用 NULL 是錯誤的,因為指向不同資料型別的指標的大小可能不同。(short *)NULL 可以使用,但沒有好處,而且可能會讓不注意的讀者認為強制轉換不是必需的。
另一個需要考慮的是應用於未命名引數的預設引數提升。float 會自動提升為 double。同樣,型別比 int 窄的引數將提升為 int 或 unsigned int。接收未命名引數的函式必須期望提升後的型別。
GCC 有一個擴充套件可以檢查傳遞的引數
format(archetype, string-index, first-to-check)- format 屬性指定函式接受
printf、scanf、strftime或strfmon樣式的引數,這些引數應根據格式字串進行型別檢查。例如,宣告會導致編譯器檢查對extern int my_printf (void *my_object, const char *my_format, ...) __attribute__ ((format (printf, 2, 3)));my_printf的呼叫中的引數,以確保它們與printf樣式的格式字串引數my_format一致。—"5.27 Extensions to the C Language Family - Declaring Attributes of Functions". Retrieved 2009-01-03.
#include <stdio.h>
#include <stdarg.h>
/* print all non-negative args one at a time;
all args are assumed to be of int type */
void printargs(int arg1, ...)
{
va_list ap;
int i;
va_start(ap, arg1);
for (i = arg1; i >= 0; i = va_arg(ap, int))
printf("%d ", i);
va_end(ap);
putchar('\n');
}
int main(void)
{
printargs(5, 2, 14, 84, 97, 15, 24, 48, -1);
printargs(84, 51, -1);
printargs(-1);
printargs(1, -1);
return 0;
}
該程式將產生以下輸出
5 2 14 84 97 15 24 48 84 51 1
要從函式內部呼叫其他可變引數函式(如 sprintf),您需要使用該函式的可變引數版本(本例中為 vsprintf)
void MyPrintf(const char* format, ...)
{
va_list args;
char buffer[BUFSIZ];
va_start(args,format);
vsprintf (buffer, format, args );
FlushFunnyStream(buffer);
va_end(args);
}
POSIX 定義了遺留標頭檔案 varargs.h,它可以追溯到 C 標準化之前,並提供與 stdarg.h 相似的功能。這個標頭檔案不是 ISO C 的一部分。該檔案,如 Single UNIX Specification 的第二個版本中定義的那樣,只包含 C89 stdarg.h 的所有功能,但以下情況除外:它不能用於標準 C 新式定義;您可以選擇不傳遞給定引數(標準 C 要求至少傳遞一個引數);它的工作方式也不同——在標準 C 中,您將編寫
#include <stdarg.h>
int summate(int n, ...)
{
va_list ap;
int i = 0;
va_start(ap, n);
for (; n; n--)
i += va_arg(ap, int);
va_end(ap);
return i;
}
或者使用舊式函式定義
#include <stdarg.h>
int summate(n, ...)
int n;
{
/* ... */
}
並使用以下方式呼叫
summate(0);
summate(1, 2);
summate(4, 9, 2, 3, 2);
使用 varargs.h,函式將為
#include <varargs.h>
summate(n, va_alist)
va_dcl /* no semicolon here! */
{
va_list ap;
int i = 0;
va_start(ap);
for (; n; n--)
i += va_arg(ap, int);
va_end(ap);
return i;
}
並且呼叫方式相同。
varargs.h 需要舊式函式定義,因為實現方式不同。[2]
- ↑ "IEEE Std 1003.1
stdarg.h". Retrieved 2009-07-04. - ↑ "Single UNIX Specification
varargs.h". Retrieved 2007-08-01.