1. 程式人生 > >如何使用變參函式?

如何使用變參函式?

對於C語言的初學者來說printf函式是經常被使用的。但是不知道大家在初學的時候有沒有注意到printf這個函式可以定義成這樣:

printf("%d",a);         //有兩個形參

也可以是這樣:

printf("%d %d",a,b);    //有三個形參

可以發現printf函式可以定義任意多個形參。所以printf函式是一個變參函式。

那麼,變參函式的原理究竟是什麼呢,我們如何去書寫變參函式?

在這裡我寫了一個簡單的變參函式和大家分享一下,程式碼如下:

#include <stdio.h>

#define  _AUPBND                (sizeof (int) - 1) 
#define _ADNBND (sizeof (int) - 1) #define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd))) #define MYva_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND)))) #define MYva_end(ap) (void) 0 #define MYva_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))
int sum(int n,...) //利用變參函式求和 { int buf[20]; //快取陣列,用於儲存傳入的形參 int sum = 0,i; char* t; //定義一個char*型別的變數 MYva_start(t, n); //將第二個形參的地址賦給t變數 for(i = 0;i < n;i++) //迴圈進行取後邊的形參和進行求和計算 { buf[i] = MYva_arg(t,int); //取後面的形參並將當前形參的值取出賦給buf快取陣列 sum += buf[i]; //求和運算 } MYva_end(t); //結束,將t指向NULL (雖然註釋掉對結果沒有影響,但是為了程式邏輯的嚴謹,還是加上吧。) return
sum; //返回計算結果 } main() { //sum函式第一個引數是需要求和的數的個數,後邊的是要求和的數值 printf("%d",sum(4,1,2,3,6)); }

先簡單做個說明。C編譯器通常提供了一系列處理可變引數的巨集,以遮蔽不同的硬體平臺造成的差異,增加程式的可移植性。這些巨集包括va_start、va_arg和va_end等。但是我們只是寫一個簡單的變參函式,巨集的名字等當然可以自己隨便取啦。

我猜初學者看到程式碼開頭定義的巨集函式,一定會蒙圈的。那麼我們就來簡單分析一下。

眾所周知,函式的形參是通過棧來傳遞的。所以說,變參函式至少有一個引數是確定的。假定我們的棧是向低地址生長的(x86架構的計算機棧是向低地址生長),而函式入棧的方向是從右向左入棧。也就是說,最後一個形參最先入棧。所以,呼叫函式sum(4,1,2,3,6)之後的棧是這樣的:

(低地址在上) 棧:
第一個形參: 4
第二個形參: 1
最後一個形參: 6

因為現在函式只知道第一個形參的資料型別和值,所以就需要順藤摸瓜,通過那幾個巨集函式在棧中找到後面的n個形參。

知道了這些,變參函式的原理就基本掌握了,那幾個巨集函式理解起來也不是那麼費勁了。

我們以MYva_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))這個為例:

此巨集函式的作用是將ap指標指向第一個不確定的形參,以我們的函式sum(4,1,2,3,6)為例,就是由第一個形參,根據棧的原理,找到第二個形參的地址。所以呼叫此巨集函式之後,ap的地址就指向了引數’1’的地址。我們的程式在執行過程中,定義了char* t,將t作為實參傳入了這個巨集函式中,所以現在t就指向了引數’1’。

簡單點講,((char *) &(A))就是獲取了第一個引數的地址,(_bnd (A,_AUPBND))計算出了A所佔的地址空間,兩者相加就得到了下一個引數的地址。所以我們將第一個形參n作為實參傳入此巨集函式進行計算。

到了這裡大家可能又會有一個疑問。計算A佔的地址空間,直接(sizeof (A))不就完了嗎?為什麼還要(_bnd (A,_AUPBND))呢?

我們找到了_bnd這個巨集函式的定義:
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))

這樣做其實是對sizeof後的結果進行的向上取了一個最接近bnd+1的倍數的數。bnd又是什麼呢,我們找到bnd對應的實參_AUPBND,進而找到了_AUPBND的巨集定義:
#define _AUPBND (sizeof (int) - 1)

所以我們知道巨集函式_bnd其實是對sizeof後的結果向上取了一個sizeof (int)的倍數的數。假設你的cpu執行sizeof (int)的結果為4,再假設你在巨集函式_bnd傳入的X經過sizeof (X)計算的結果為17,那麼巨集函式_bnd的結果為就20。為什麼會這樣呢?因為棧的最小單元就是sizeof (int)。

好了,介紹完MYva_start(ap, A)這個巨集函式的原理,相信其他巨集函式也可以理解了。進而就可以掌握變參函式的原理。

但是變參函式由於語法並不是非常嚴謹,比如當你呼叫printf(“%d”,a,b);的時候,b就被忽略了,可能得不到你想要的結果,在應用中,變參函式也大多能被代替,所以變參函式的實際應用價值可能沒那麼大。

本人也是C語言的初學者,菜鳥一枚,如果出現錯誤或者不合適的地方,請斧正,謝謝。