cstdarg中可變引數的實現

2021-07-08 16:57:09 字數 3775 閱讀 1889

在c++中,可變引數有兩種型別:

1. 型別不變,引數個數可變

2. 引數個數可變,型別可變

今天著重講述第1種:型別不變,引數個數可變。

先看乙個例子:

#include 

#include

void show(int length, ...)

int main()

執行結果:

11 22 33

通過觀察執行結果,會發現:

1. 傳入的第乙個引數是後面需要列印的引數的個數

2. 引數列表中使用了 … 來表示多個引數

3. 使用了多個巨集

我們為了準確列印引數的個數而傳入了引數的個數,其實我們還有另外一種做法:傳入乙個終止標識

#include 

#include

void show();

void show(int arg1,...)

int main()

執行結果:

11 22 33

第二種做法:稍微修改一下迴圈

#include 

#include

void show();

void show(int arg1, ...)

int main()

由此,我們可以總結出cstdarg標頭檔案當中可變引數巨集的用法的一般步驟:

1. va_list ap; 建立乙個指向棧中儲存引數列表位置的char型別指標ap

2. va_start(ap,v); 初始化ap,讓ap指向與v相鄰的下乙個引數

3. va_arg(ap,type); 傳入ap,並且傳入引數的型別type,乙個乙個地取出儲存在棧中的引數。

4. va_end(ap); 操作結束,將ap置為null

這是實現**當中的一種:

#include 

//獲取傳入引數的位址

#define _addressof(v) (&reinterpret_cast(v))

//獲取與int對齊的元素大小,比如 1位元組=4位元組 2位元組=4位元組 5位元組=8位元組

//因為某些系統的棧實現時遵循記憶體對齊的規則

#define _intsizeof(v) ((sizeof(v)+sizeof(int)-1)&~(sizeof(int)-1))

//定義乙個字元型指標,指向某個引數的起始位址

#define va_list char*

//當ap用完時,置空ap,防止出現野指標

#define va_end(ap) (ap=reinterpret_cast(0))

//初始化指向棧中引數列表的指標ap,讓他指向v的下乙個引數的位置

#define va_start(ap,v) (ap=const_cast(_addressof(v))+_intsizeof(v))

//通過乙個指向棧的指標ap,乙個乙個的取出引數列表中的引數

#define va_arg(ap,t) (*reinterpret_cast((ap+=_intsizeof(t))-_intsizeof(t)))

//使用它

void show(int a,...)

接下來我們將會一步一步地解析它實現的過程。

由此,我們可以總結出cstdarg標頭檔案當中可變引數巨集的用法的一般步驟:

1. va_list ap; 建立乙個指向棧中儲存引數列表位置的char型別指標ap

2. va_start(ap,v); 初始化ap,讓ap指向與v相鄰的下乙個引數

3. va_arg(ap,type); 傳入ap,並且傳入引數的型別type,乙個乙個地取出儲存在棧中的引數。

4. va_end(ap); 操作結束,將ap置為null

我們可以總結出實現可變引數需要的操作分別有哪些:

va_list 我們要建立乙個ap,指向棧 -> 需要:建立ap的操作

有些棧的實現遵循記憶體對齊原則 -> 需要:記憶體對齊操作

va_arg 我們 -> 需要:乙個乙個地取出引數列表中的引數

va_end 置空ap -> 需要:置空我們建立的指標ap

—- 步驟詳解:—-

為什麼使用reinterpret_cast,因為該轉換可以實現兩種完全不相關的型別的之間的轉換(重新闡釋位元位),比如:傳入int型別,但是需要讓ap指向int資料的起始位址,此時需要將int型別轉換為int的const引用,這樣我們取得的位址將會是const型別的,可以保證棧中資料不被意外修改

const_cast去掉取出位址中的const屬性,在賦值給ap(char*型別)之前,「+_intsizeof(v)」操作就是將指標移動到v後面的乙個元素的起始位址。

參考博文:cc++可變引數stdarg.h中的餘數運用

ap指向的就是棧中的某個元素的起始位址,此時我們只需將指標轉換為t型別,最後用*取出該資料即可。

關於「(ap+=_intsizeof(t)」是為了讓ap移動到下乙個引數的起始位址,返回之前「-_intsizeof(t)」就是由於前面+=操作將指標移動到了下乙個引數的記憶體位址,所以我們要將之減去後再取出。

將null的賦值給ap

以上就是cstdarg的實現。

#include 

//獲取傳入引數的位址

#define _addressof(v) ((const char*)&v)

//獲取與int對齊的元素大小,比如 1位元組=4位元組 2位元組=4位元組 5位元組=8位元組

//因為某些系統的棧實現時遵循記憶體對齊的規則

#define _intsizeof(v) ((sizeof(v)+sizeof(int)-1)&~(sizeof(int)-1))

//定義乙個字元型指標,指向某個引數的起始位址

#define va_list char*

//當ap用完時,置空ap,防止出現野指標

#define va_end(ap) (ap=(va_list)0)

//初始化指向棧中引數列表的指標ap,讓他指向v的下乙個引數的位置

#define va_start(ap,v) (ap=(va_list)(_addressof(v))+_intsizeof(v))

//通過乙個指向棧的指標ap,乙個乙個的取出引數列表中的引數

#define va_arg(ap,t) (*(t*)((ap+=_intsizeof(t))-_intsizeof(t)))

//使用它

void show(int a,...)

int main()

C C 中可變引數函式的實現

在c語言的stdarg.h標頭檔案中提供了三個函式va start,va end,va arg和乙個型別va list。利用它們,我們可以很容易實現乙個可變引數的函式。首先簡單介紹一下這三個函式。假設現在有乙個名為f的函式,其函式定義為 void f int a,int b,那麼,在函式的內部,為了...

C C 中可變引數函式的實現

在c語言的stdarg.h標頭檔案中提供了三個函式va start,va end,va arg和乙個型別va list。利用它們,我們可以很容易實現乙個可變引數的函式。首先簡單介紹一下這三個函式。假設現在有乙個名為f的函式,其函式定義為 void f int a,int b,那麼,在函式的內部,為了...

C C 中可變引數函式的實現

在c語言的stdarg.h標頭檔案中提供了三個函式va start,va end,va arg和乙個型別va list。利用它們,我們可以很容易實現乙個可變引數的函式。首先簡單介紹一下這三個函式。假設現在有乙個名為f的函式,其函式定義為 void f int a,int b,那麼,在函式的內部,為了...