天天看點

關于可變參數中需要用到的宏va_start,va_arg,va_end

關于可變參數中需要用到的宏

⑴在可變參數中将用到以下這些宏:

void va_start( va_list arg_ptr, prev_param );

type va_arg( va_list arg_ptr, type );

void va_end( va_list arg_ptr );
           

va在這裡是variable-argument(可變參數)的意思.

這些宏定義在stdarg.h中,是以用到可變參數的程式應該包含這個頭檔案.

⑵函數裡首先定義一個va_list型的變量,這裡是arg_ptr,這個變

量是存儲參數位址的指針.因為得到參數的位址之後,再結合參數的類型,才能得到參數的值。

⑶然後用va_start宏初始化⑵中定義的變量arg_ptr,這個宏的第二個參數是可變參數清單的前一個參數,即最後一個固定參數.

⑷然後依次用va_arg宏使arg_ptr傳回可變參數的位址,得到這個位址之後,結合參數的類型,就可以得到參數的值。

⑸設定結束條件,這裡的條件就是判斷參數值是否為-1。注意被調的函數在調用時是不知道可變參數的正确數目的,程式員必須自己在代碼中指明結束條件。至于為什麼它不會知道參數的數目,讀者在看完這幾個宏的内部實作機制後,自然就會明白。

(二)可變參數在編譯器中的處理

我們知道va_start, va_arg,va_end是在stdarg.h中被定義成宏的, 由于1)硬體平台的不同 2)編譯器的不同,是以定義的宏也有所不同,下面看一下VC++6.0中stdarg.h裡的代碼(檔案的路徑為VC安裝目錄下的\vc98\ include\stdarg.h)

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()的實作就應該心中有數了:

這個宏做了兩個事情,

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

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

(5) va_end宏的解釋:x86平台定義為ap=(char*)0;使ap不再指向堆棧,而是跟NULL一樣.有些直接定義為((void*)0),這樣編譯器不會為va_end産生代碼,例如gcc在linux的x86平台就是這樣定義的. 在這裡大家要注意一個問題:由于參數的位址用于va_start宏,是以參數不能聲明為寄存器變量或作為函數或數組類型. 關于va_start, va_arg, va_end的描述就是這些了,我們要注意的 是不同的作業系統和硬體平台的定義有些不同,但原理卻是相似的.

(6)關于((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))的解釋

先看一個日常生活中的問題,

問題1:假設有要把一批貨物放到集裝箱裡,貨物有12件,

一個箱子最多能裝6件貨物,求箱子的數目。

解答:顯然我們需要12/6=2個箱子,并且每個箱子都是滿的。這個連國小生都會算:-)

問題2: 把問題1的條件改一下,假設一個箱子最多能裝5件貨物,那麼現在的箱子數是多少?

解答: 12/5=2.4個,但是根據實際情況,箱子的個數必須為整數,(有不知道這個常識的就不要再往下看了,

回國小重讀吧,呵呵)自然我們就要取3,

下面把問題一般化

三、一般數學模型

問題3:設一個箱子最多可以裝M件貨物,且現有N件貨物,

則至少需要多少個箱子,給出一般的計算公式。

這裡要注意兩點

1、箱子的總數必須為整數

2、N不一定大于M,很顯然,即使N

#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) – 1) & ~(sizeof(int) – 1)
           

~是位取反的意思。

_INTSIZEOF(n)整個做的事情就是将n的長度化為int長度的整數倍。

比如n為5,二進制就是101b,int長度為4,二進制為100b,那麼n化為int長度的整數倍就應該為8。

~(sizeof(int) – 1) )就應該為~(4-1)=~(00000011b)=11111100b,這樣任何數& ~(sizeof(int) – 1) )後最後兩位肯定為0,就肯定是4的整數倍了。

(sizeof(n) + sizeof(int) – 1)就是将大于4m但小于等于4(m+1)的數提高到大于等于4(m+1)但小于4(m+2),這樣再& ~(sizeof(int) – 1) )後就正好将原長度補齊到4的倍數了。

繼續閱讀