C語言的變參技術 va arg

2021-05-22 13:30:49 字數 2764 閱讀 8778

#include // 必須包含的標頭檔案

int add(int start,...)   // ...是作為佔位符

while(nargvalue != 0);                      // 判斷結束條件;結束條件是自定義為=0時結束

va_end(arg_ptr);   // 復位指標

return sum;

}函式的呼叫方法為add(1,2,3,0);這樣,必須以0結尾,因為變參函式結束的判斷條件就是讀到0停止。

解釋:所使用到的巨集:

void va_start( va_list arg_ptr, prev_param );

type va_arg( va_list arg_ptr, type );

void va_end( va_list arg_ptr );

typedef char *   va_list;

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

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

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

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

1、首先把va_list被定義成char*,這是因為在我們目前所用的pc機上,字元指標型別可以用來儲存記憶體單元位址。而在有的機器上va_list是被定義成void*的

2、定義_intsizeof(n)主要是為了某些需要記憶體的對齊的系統.這個巨集的目的是為了得到最後乙個固定引數的實際記憶體大小。在我的機器上直接用sizeof運算子來代替,對程式的執行結構也沒有影響。(後文將看到我自己的實現)。

3、va_start的定義為 &v+_intsizeof(v) ,這裡&v是最後乙個固定引數的起始位址,再加上其實際占用大小後,就得到了第乙個可變引數的起始記憶體位址。所以我們執行va_start(ap, v)以後,ap指向第乙個可變引數在的記憶體位址,有了這個位址,以後的事情就簡單了。

這裡要知道兩個事情:

⑴在intel+windows的機器上,函式棧的方向是向下的,棧頂指標的記憶體位址低於棧底指標,所以先進棧的資料是存放在記憶體的高位址處。

(2)在vc等絕大多數c編譯器中,預設情況下,引數進棧的順序是由右向左的,因此,引數進棧以後的記憶體模型如下圖所示:最後乙個固定引數的位址位於第乙個可變引數之下,並且是連續儲存的。

|--------------------------|

|   最後乙個可變引數              |    ->高記憶體位址處

|--------------------------|

|--------------------------|

|   第n個可變引數               |      ->va_arg(arg_ptr,int)後arg_ptr所指的地方,

|                                |      即第n個可變引數的位址。

|--------------- |     

|--------------------------|

|   第乙個可變引數                |      ->va_start(arg_ptr,start)後arg_ptr所指的地方

|                                |      即第乙個可變引數的位址

|--------------- |     

|------------------------ --|

|                                |

|   最後乙個固定引數              |     -> start的起始位址

|-------------- -|        .................

|-------------------------- |

|                                |  

|--------------- |   -> 低記憶體位址處

(4) va_arg():有了va_start的良好基礎,我們取得了第乙個可變引數的位址,在va_arg()裡的任務就是根據指定的引數型別取得本引數的值,並且把指標調到下乙個引數的起始位址。

因此,現在再來看va_arg()的實現就應該心中有數了:

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

這個巨集做了兩個事情,

①用使用者輸入的型別名對引數位址進行強制型別轉換,得到使用者所需要的值

②計算出本引數的實際大小,將指標調到本引數的結尾,也就是下乙個引數的首位址,以便後續處理。

(5)va_end巨集的解釋:x86平台定義為ap=(char*)0;使ap不再 指向堆疊,而是跟null一樣.有些直接定義為((void*)0),這樣編譯器不會為va_end產生**,例如gcc在linux的x86平台就是這樣定義的. 在這裡大家要注意乙個問題:由於引數的位址用於va_start巨集,所以引數不能宣告為暫存器變數或作為函式或陣列型別. 關於va_start, va_arg, va_end的描述就是這些了,我們要注意的 是不同的作業系統和硬體平台的定義有些不同,但原理卻是相似的.

--**網易:懶羊羊的南瓜屋

C語言變參使用

c語言中有很多變參的使用,例如printf 的原型是int printf const char fmt,那麼c語言是如何解析和處理這些變參的呢?下面進行簡單的總結 c語言中定義了下面的一些巨集,專門用來處理變參 va start va list ap,char fmt va arg va list ...

C語言(變參函式)

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

C語言變參,記錄

由於在 c語言中沒有函式過載 解決不定數目函式引數問題變得比較麻煩 即使採用 c 如果引數個數不能確定 也很難採用函式過載 對這種情況 有些人採用指標引數來解決問題.uhmm 用到的變參地方 用的原因 1,sql 語言必須用常量2,sql語言格式不相同,引數個數不相同使用a dyw mysql nu...