C語言變參函式實現方法分析

2021-06-26 09:15:49 字數 3812 閱讀 2488

c語言中,最經典的可變引數函式莫過於printf(),它的宣告如下所示:

int printf(const char *format, ...);
printf()的引數列表中,第1個引數是const char* format,而第2個引數是3點「...」,兩者用逗號隔開。這就是可變參函式的宣告方式。那麼,第1個引數和後續的若干引數之間是什麼關係呢?可以寫個程式來測試一下。

#include void function(const char *arg, ...);

int main(int argc, char *argv)

void function(const char *arg, ...)

編譯程式,執行結果如下:

圖 1結論:c語言的可變引數列表是乙個字串陣列,arg是第1個字串,其它字串引數緊跟在後面。

需要注意的是,arg是字串而不是字串陣列!由於陣列的特性,知道第1個元素就可以依次訪問剩餘的元素,故知道第1個元素的位址即可!

知道了上述結論,已經可以自定義自己想要的可變參函式。但是每次都要通過(&arg)[0]的形式來訪問引數,不是很方便。因此,可以定義一些巨集來簡化引數的訪問。具體實現如下:

#include // 為了簡化可變引數的訪問,定義一下型別和巨集

typedef char * va_list;

#define _intsizeof(v) ( (sizeof(v) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) // 32位系統中,此巨集的結果恒為4

#define va_start(ap,v) ( ap = (va_list)&v + _intsizeof(v) ) // 初始化ap使其指向第2個引數(忽略第1個!)

#define va_arg(ap,t) ( *(t *)((ap += _intsizeof(t)) - _intsizeof(t)) ) // 將當前的字串返回,並將ap指向下乙個字串

#define va_end(ap) ( ap = (va_list)0 ) // 將ap賦為char *即null

void function(const char *arg, ...);

int main(int argc, char *argv)

void function(const char *arg, ...)

編譯程式,執行結果如下:

圖2結論:這個結果和圖1的結果基本相同。但需要注意的是圖2中hello只輸出一次,因為第6行**已經說明,忽略第1個引數。另外,圖2的最後乙個輸出為亂碼,那是因為function的輸入只有3個引數,而最後乙個輸出對應的是第4個引數,故亂碼。

那麼,上面的巨集分別代表什麼意思呢?下面進行詳細的分析。

#define _intsizeof(v) ( (sizeof(v) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
對比其巨集名和sizeof()可知,該巨集主要用於獲取型別占用的空間長度,但與sizeof()不同的是,此巨集獲取的最小占用長度為int的整數倍。例如:在32位系統中sizeof(int)=4,因此~(sizeof(int) -1) 的二進位制最低兩位為0,任何數與其作位與則使得末兩位為0!在32為系統中,當v為char型時,sizeof(v) + sizeof(int) -1 = 4,位與後結果為4;當v為short型時,sizeof(v) + sizeof(int) -1 = 5,位與後結果為4;當v為int型或者指標時,sizeof(v) + sizeof(int) -1 = 7,位與後結果為4;當v為double型,sizeof(v)為8,最終位與的結果是8。這些結果都是4的整數倍!

#define va_start(ap,v) ( ap = (va_list)&v + _intsizeof(v) )
此巨集主要用於獲取可變引數列表的第2個引數的位址(ap是型別為va_list的指標,v是可變引數第1個引數)。為什麼是第2個引數,不是第1個呢?下面將詳細分析。由定義知v為char*型別,因此&v為char**型別,即引數列表陣列。然而,為了避免賦值時型別衝突,將&v強制轉換為va_list型別,也就是char*型,這是乙個非常有意思的做法。因此,ap並不是第1個字串的位址!可以用下面的程式進行測試。

#include // 為了簡化可變引數的訪問,定義一下型別和巨集

typedef char * va_list;

#define _intsizeof(v) ( (sizeof(v) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) // 32位系統中,此巨集的結果恒為4

#define va_start(ap,v) ( ap = (va_list)&v/* + _intsizeof(v) */) // 僅測試(va_list)&v的效果,故先注釋+ _intsizeof(v)

#define va_arg(ap,t) ( *(t *)((ap += _intsizeof(t)) - _intsizeof(t)) ) // 將當前的字串返回,並將ap指向下乙個字串

#define va_end(ap) ( ap = (va_list)0 ) // 將ap賦為char *即null

void function(const char *arg, ...);

int main(int argc, char *argv)

void function(const char *arg, ...)

編譯**,執行結果如下:

可見,將ap當做字串的輸出時亂碼,而現將其轉換為char**再用下標的方式進行訪問,則可以得到正確的字串輸出。

上面提到,va_list主要用於獲取可變引數列表的第2個引數的位址,那是因為&v加了_intsizeof(v)再賦值給ap,從而使得ap指向的是第2個引數,而不是第1個。為什麼要這樣做呢?那是因為第1個引數可以由arg獲得,不必再過va_list來獲取了!

#define va_arg(ap,t) ( *(t *)((ap += _intsizeof(t)) - _intsizeof(t)) )
此巨集主要用於獲取可變引數的當前引數,返回指定值,並將指標指向下一引數(t——當前引數的型別)。最裡層的(ap += _intsizeof(t)) - _intsizeof(t),表示先將ap增加_intsizeof(t)的值,而再減_intsizeof(t)則表示增加前的值了,然後對其進行型別轉換(t *)。對於字串,t為char*,因此(t*)就是(char **)。最後對char**型別進行取值運算,則變成char*型。好曲折,但是很巧妙吧!

#define va_end(ap) ( ap = (va_list)0 )
這個巨集幾比較簡單了,主要用於清空va_list可變引數列表。將ap賦值為(va_list)0型別,也即是(char *)0型別,即null!這時ap不再有效!

這些巨集確實方便了可變參函式的實現,但是想不出這麼巧妙的巨集怎麼辦呢?沒關係,別人早就已經想好了,而且已經實現了,上述的巨集都在標頭檔案stdarg.h中!使用時只需要包含此標頭檔案即可!

參考資料

[2]c語言可變引數函式詳解示例

C語言(變參函式)

c語言雖然沒有c 的函式過載特性,但也可以實現變參,但要保證第乙個引數資訊的完整性。拓展 定義變參函式時,第乙個引數一般是字串,攜帶後續變參的型別和數量資訊,變參使用三點來表示,如 void sumup const char info,再使用va list va start va arg 和va e...

C語言變參函式巨集定義分析

在c語言變參函式中總是會用到下面幾個巨集 0 define adnbnd sizeof acpi native int 1 1 define bnd x,bnd sizeof x bnd bnd 2 define va arg ap,t t ap bnd t,aupbnd bnd t,adnbnd ...

C語言變參函式的實現原理

1.變參函式簡單示例 include include int accumlate int nr,va end arg return result int main 2.變參函式的實現原理 define va list void define va start arg,start arg va lis...