我們用的最多的c函式是哪個?毫無疑問,是printf。但是你看過printf的宣告式嗎,那是相當詭異。隨便拿一本帶c庫函式參考的書,可查到如下結果:
int printf(const char *format, ...);
那三個連續的點就代表大於或等於0個引數,再加上前面的format引數,所以printf函式至少要接受乙個字串,後面就隨便了。但是這是如何實現的呢,不急,首先必須要了解c語言函式引數傳遞的機制。
c語言函式引數傳遞機制
我們知道,每個程式都有個使用者棧,所有的函式呼叫都要使用它,比如被呼叫函式的實參及區域性變數都要存在使用者棧中,所以每個函式都有個棧幀。下面我用兩個函式來說明這種呼叫機制:
int main(void) int add(int param1, int param2)
return 0;
}main 函式在呼叫add函式時,main負責把b,a及eip(即返回位址)按順序壓棧,注意棧是向下增長的。然後執行call指令呼叫add函式。(ps: 其實在返回位址和區域性變數d之間函式add還會壓入一些暫存器,但是這對我們理解變長引數沒有影響,總之要記住引數是自右向左壓棧的)
處理變長引數的巨集定義
處理變長引數說白了就是怎樣獲取那些在宣告中沒明說的引數,比如在printf("%d equals to %d.", a, b)中,第乙個字串引數可直接通過format取得,但a和b呢?這就要需要對引數傳遞的理解了。主函式呼叫printf時肯定是先壓入b,再壓入a,最後壓入"%d equals to %d"的位址。
現在我們來獲取a的位址,很簡單,當然是 (char*)&format+sizeof(const char*)。其中&format是引數format在棧中的位址,而a正好format上面。
ok, 現在我們可以系統地講解處理變長引數的巨集了。
在標頭檔案stdarg.h中先定義個型別 va_list,它是專門用來去可變引數的,如果要對付可變引數首先要定義乙個va_list型別的變數ap:
typedef char* va_list;
因為在獲取引數位址時必須知道它前乙個引數的資料型別大小,所以要定義__va_sizeof(type),它的作用和sizeof一樣,不過__va_sizeof(char),__va_sizeof(short)的值都是4:
#define __va_sizeof(type) (((sizeof(type) + sizeof(int) - 1) / sizeof(int)) * sizeof(int))
#define va_start(ap, last) ((ap) = (va_list)&(last) + __va_size(last))
va_arg用於獲取ap當前指向的引數的值,並順帶把va_list移向下乙個引數
#define va_arg(ap, type) (*(type *)((ap) += __va_size(type), (ap) - __va_size(type)))
當用完ap時,用va_end將其賦值為空,也就是0
#define va_end(ap) (ap)=0
處理變長引數的一般框架
如果我們自己實現printf函式,**的形式大概如下
int chparamf(const char *format, ...)00
0(請您對文章做出評價)
C語言變長引數實現
include include include 編寫可變長引數列表的函式案例 void minprintf char fmt,這個函式只處理格式字串和引數,格式的轉換則通過printf函式實現 省略號表示引數的數量和型別是可變的,省略號只能出現再參數列的尾部,minprintf不需要像printf ...
python變長引數列表 可變長引數
可變長引數 預設情況下,必須使用正確數量的引數呼叫函式,這意味著,如果您的函式需要2個引數,則必須使用2個引數 不多也不少 來呼叫函式。示例,該函式需要2個引數,並獲得2個引數 def my function fname,lname print fname lname my function emi...
變長引數的 Tracer
幾天前,在csdn論壇看到這麼一則討論 在巨集定義中怎麼使用可變引數?http expert.csdn.net expert topic 2925 2925165.xml 樓主希望能定義這樣的macro define fun1 a,b,fun2 file line a,b,我猜樓主是想寫trace,...