很多語言都支援帶變長引數的函式,c也不例外,我們常用的比如printf()函式,它的函式原型中引數列表裡面有乙個省略號,就代表了可變引數列表,可以先看下它的實現:
int printf(char *fmt, ...)
可能你現在還不明白這段**,但大概功能能看出來吧,先是把可變引數傳給vsprintf,由它負責將內容輸出到buf中,然後將buf列印到螢幕上。
但有人可能會對以va_開頭的那幾個函式可能會比較陌生,下面我列出了它們的函式原型:
#include voidva_start(va_list ap, argn);
type va_arg(va_list ap, type);
void
va_copy(va_list dest, va_list src);
void va_end(va_list ap);
實際上,這幾個函式都不是函式,而是巨集定義:
typedef char *va_list;#define _aupbnd (sizeof (acpi_native_int) - 1)
#define _adnbnd (sizeof (acpi_native_int) - 1)
#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))))
從上面的巨集看好像它們都是在計算一些偏移位置而已。ok,先回過頭來看看函式呼叫的過程,然後就會明白這些偏移值的計算原理。
我們知道計算機中函式呼叫有兩種方式,分別是stdcall與cdecl,它們的區別在於:
1、_stdcall 是pascal程式的預設呼叫方式,通常用於 win32 api中。按從右至左的順序壓引數入棧。 在主調函式中負責壓棧,在被調函式中返回前負責清理棧。 由於被調函式並不知道傳進來的引數個數,因此 _stdcall不適合可變長度引數的函式。
2、_cdecl (the c default calling convention)是c/c++ 呼叫約定,也是按從右至左的順序壓引數入棧,並且由呼叫者把彈出棧,因此,實現可變引數的函式只能使用該呼叫約定。
顯然,我們的可變引數使用了_cdecl協議呼叫函式,發生函式呼叫時,主調函式將引數按照從右往左的順序壓入堆疊,被調函式返回後,主調函式還要對棧進行清理。
看乙個簡單的例子:
#include void fun(intn, ...)
va_end(arg);
}int
main()
首先,宣告了乙個 va_list(就是char*指標)的引數列表args,
va_list args;
然後用va_start 巨集來獲取引數列表中的引數,這裡args引數是剛宣告的乙個char*指標,自不必多說,n是位於棧頂的固定引數。
va_start(args, n);
我們知道棧是由高位址向低位址生長,va_start使arg指向了第乙個可選引數,
va_arg(args, int);
每次呼叫va_arg之後(需要指定下乙個引數的型別),args就指向下乙個引數,這就是前面那一坨巨集定義幹的事情。
現在我們再來想想printf()函式,它的第乙個引數是fmt,
根據'%'就能確定後面的可變引數個數和每個引數的型別,這樣就能通過va_arg依次從棧中定位到每個引數的位置。
(夜深了,還是先睡吧,未完待續。。。)
可變長函式引數
1.1 什麼是可變長引數 可變長引數 顧名思義,就是函式的引數長度 數量 是可變的。比如 c 語言的 printf 系列的 格式化輸入輸出等 函式,都是引數可變的。下面是 printf 函式的宣告 int printf const char format,可變引數函式宣告方式都是類似的。1.2 如何...
變長引數列表函式
可變引數列表 標頭檔案提供了遍歷未知數目和型別的函式引數表的功能。該標頭檔案的實現因不同的機器而不同,但提供的介面是一致的。假定函式 f 帶有可變數目的實際引數,lastarg 是它的最後乙個命名的形式引數 引數列表必須至少包括乙個命名引數 那麼,在函式 f 內宣告乙個型別為 va list 的變數...
C 變長引數函式小結
變長引數的函式,即引數個數可變 引數型別不定的函式。設計乙個引數個數可變 引數型別不定的函式是可能的,最常見的例子是printf函式 scanf函式和高階語言的format函式。在c c 中,為了通知編譯器函式的引數個數和型別可變 即是不定的 未知的 就必須以三個點結束該函式的宣告。例如 print...