在ANSI C中,這些宏的定義位於stdarg.h中,典型的實現如下:
typedef char *va_list;
va_start宏,獲取可變參數列表的第一個參數的地址(list是類型為va_list的指針,param1是可變參數最左邊的參數):
#define va_start(list,param1) ( list = (va_list)¶m1+ sizeof(param1) )
va_arg宏,獲取可變參數的當前參數,返回指定類型並將指針指向下一參數(mode參數描述了當前參數的類型):
#define va_arg(list,mode) ( (mode *) ( list += sizeof(mode) ) )[-1]
va_end宏,清空va_list可變參數列表:
#define va_end(list) ( list = (va_list)0 )
注:以上sizeof()只是為了說明工作原理,實際實現中,增加的字節數需保證為為int的整數倍
如:#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
為了理解這些宏的作用,我們必須先搞清楚:C語言中函數參數的內存布局。首先,函數參數是存儲在棧中的,函數參數從右往左依次入棧。
以下面函數為討論對象:
void test(char *para1,char *param2,char *param3, char *param4) { va_list list; ...... return; }
在linux中,棧由高地址往低地址生長,調用test函數時,其參數入棧情況如下:
當調用va_start(list,param1) 時:list指針指向情況對應下圖:
最復雜的宏是va_arg。它必須返回一個由va_list所指向的恰當的類型的數值,同時遞增va_list,使它指向參數列表中的一個參數(即遞增的大小等於與va_arg宏所返回的數值具有相同類型的對象的長度)。因為類型轉換的結果不能作為賦值運算的目標,所以va_arg宏首先使用sizeof來確定需要遞增的大小,然后把它直接加到va_list上,這樣得到的指針再被轉換為要求的類型。因為該指針現在指向的位置"過"了一個類型單位的大小,所以我們使用了下標-1來存取正確的返回參數。
下面是實際用例:
#include <stdio.h> #include <stdarg.h> void var_test(char *format, ...) { va_list list; va_start(list,format); char *ch; while(1) { ch = va_arg(list, char *); if(strcmp(ch,"") == 0) { printf("\n"); break; } printf("%s ",ch); } va_end(list); } int main() { var_test("test","this","is","a","test",""); return 0; }
附:可變參數應用實例
1.printf實現
#include <stdarg.h> int printf(char *format, ...) { va_list ap; int n; va_start(ap, format); n = vprintf(format, ap); va_end(ap); return n; }
2.定制錯誤打印函數error
#include <stdio.h> #include <stdarg.h> void error(char *format, ...) { va_list ap; va_start(ap, format); fprintf(stderr, "Error: "); vfprintf(stderr, format, ap); va_end(ap); fprintf(stderr, "\n"); return; }