跳轉到內容

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

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)
格式屬性指定函式採用 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". 檢索於 2009-07-04.
  2. "Single UNIX Specification varargs.h". 檢索於 2007-08-01.
華夏公益教科書