詳解可變引數列表

2021-07-11 08:13:30 字數 3362 閱讀 8675

可變引數  

至少有乙個引數 例如: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 可變引數列表 為什麼需要可變引數呢?在函式原型中,列出了函式期望接受的引數,原型只能顯示固定數目的引數,如果函式原型列出的引數與可呼叫引數不匹配或數目不一樣,程式將無法執行。例如,我們想要求一系列值的平均值,這裡的一系列的數目是不確定的,如果這些值儲存於陣列中,這個任務就...