跳轉到內容

C 程式設計/stdarg.h

來自 Wikibooks,開放的書籍,開放的世界

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;
{
    /* ... */
}

stdarg.h 型別

[編輯 | 編輯原始碼]
名稱 描述 相容性
va_list 用於迭代引數的型別 C89

stdarg.h

[編輯 | 編輯原始碼]
名稱 描述 相容性
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 中。

沒有定義用於確定傳遞給函式的未命名引數的數量或型別的機制。函式只需要以某種方式知道或確定這一點,具體方法各不相同。常見的約定包括

  • 使用 printfscanf 樣式的格式字串,其中包含指示引數型別的嵌入式說明符。
  • 可變引數結尾處的哨兵值。
  • 一個表示可變引數數量的計數引數。

型別安全

[編輯 | 編輯原始碼]

一些 C 實現提供 C 擴充套件,允許編譯器檢查格式字串和哨兵的正確使用。在沒有這些擴充套件的情況下,編譯器通常無法檢查傳遞的未命名引數是否與函式期望的型別匹配,或者將它們轉換為所需的型別。因此,應注意確保這方面的正確性,因為如果型別不匹配,將導致未定義的行為。

例如,如果呼叫 va_arg(ap, short *),則必須在呼叫中將該引數的空指標傳遞為 (short *)0;使用 NULL 是錯誤的,因為指向不同資料型別的指標的大小可能不同。(short *)NULL 可以使用,但沒有好處,而且可能會讓不注意的讀者認為強制轉換不是必需的。

另一個需要考慮的是應用於未命名引數的預設引數提升。float 會自動提升為 double。同樣,型別比 int 窄的引數將提升為 intunsigned int。接收未命名引數的函式必須期望提升後的型別。

GCC 有一個擴充套件可以檢查傳遞的引數

format(archetype, string-index, first-to-check)
format 屬性指定函式接受 printfscanfstrftimestrfmon 樣式的引數,這些引數應根據格式字串進行型別檢查。例如,宣告
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);
}

varargs.h

[編輯 | 編輯原始碼]

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]

參考文獻

[編輯 | 編輯原始碼]
  1. "IEEE Std 1003.1 stdarg.h". Retrieved 2009-07-04.
  2. "Single UNIX Specification varargs.h". Retrieved 2007-08-01.
華夏公益教科書