關于可變參數中需要用到的宏
⑴在可變參數中将用到以下這些宏:
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的倍數了。