可變引數
至少有乙個引數 例如:void add(int a,… ){}
例題 模擬printf()函式
#include
#include
void myprintf(const
char *format, ...)
case 's':
default:
putchar(c);
break;}}
va_end(ap);
}int main(void)
需要呼叫到 stdarg.h 標準庫的 va_list 型別和va_start、va_arg、va_end巨集
va在這裡的意思是variable argument(可變引數)
//va_list 首先在函式裡定義乙個va_list型的變數,這個變數時指向引數的指標,為訪問可變引數列表中的引數,它必須宣告乙個物件
例如: va_list
ap;
//va_start(,) 用va_start巨集初始化定義剛宣告的物件,初始化結果供va_arg巨集和va_end巨集使用
例如: va_start
(ap, format);
//va_arg
然後用va_arg返回可變的引數,va_arg的第二個引數是你要返回的引數的型別,每使用一次va_arg,va_list所宣告的變數指標往後移乙個(如果函式有多個可變引數的,依次呼叫va_arg獲取各個引數);
例如:char
ch =
va_arg
(ap,
int);
//va_end
最後用va_end巨集結束可變引數的獲取.然後你就可以在函式裡使
用第二個引數了.如果函式有多個可變引數的,依次呼叫va_arg獲
取各個引數.
可變引數在編譯器中的處理
我們知道va_start,va_arg,va_end是在stdarg.h中被定義成巨集的, 由於硬體平台的不同或編譯器的不同,所以定義的巨集也有所不同,下面是vc++6.0中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)主要是為了某些需要記憶體的對齊的系統.這個巨集的目的是為了將n的長度化為int長度的整數倍。
例如:n為5,二進位制就是101b,int長度為4,二進位制為100b,那麼n化為int長度的整數倍就應該為8。
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的描述就是這些了,我們要注意的 是不同的作業系統和硬體平台的定義有些不同,但原理卻是相似的.
可變引數列表
模擬實現printf函式 va list是在c語言中解決變參問題的一組巨集,所在標頭檔案 include 用於獲取不確定個數的引數 va start,函式名稱,讀取可變引數的過程其實就是在堆疊中,使用指標,遍歷堆疊段中的引數列表,從低位址到高位址乙個乙個地把引數內容讀出來的過程 va arg,這個巨...
可變引數列表
小二,上 class a public class varargs two param static void twostringparam string a,string b three param 參照上兩種寫法,一直往後面加。是不是感覺很憂傷 幸好這不是真的。string.a static v...
可變引數列表
測試環境 vs2008 1 可變引數列表 為什麼需要可變引數呢?在函式原型中,列出了函式期望接受的引數,原型只能顯示固定數目的引數,如果函式原型列出的引數與可呼叫引數不匹配或數目不一樣,程式將無法執行。例如,我們想要求一系列值的平均值,這裡的一系列的數目是不確定的,如果這些值儲存於陣列中,這個任務就...