具體解釋可變參數列表

可變參數  
至少有一個參數 好比:void add(int a,… ){}

例題 
模擬printf()函數


#include <stdio.h>linux

#include <stdarg.h>windows

void myprintf(constchar *format, ...)數組

{函數

    va_list ap;post

    char c;spa

    va_start(ap, format);操作系統

    while ((c = *format++))指針

    {orm

        switch(c)對象

        {

            case 'c':

            {

                char ch = va_arg(ap, int);

                putchar(ch);

                break;

            }

            case 's':

            {

                char *p = va_arg(ap, char *);

                fputs(p, stdout);

                break;

            }

            default:

                putchar(c);

                break;

        }

    }

    va_end(ap);

}


int main(void)

{

    myprintf("s ccc\n","hello",'b','i','t');

    return 0;

}




需要調用到 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 ) 
如下爲代碼的含義: 
一、首先把va_list被定義成char*,這是因爲在咱們眼下所用的PC機上,字符指針類型可以用來存儲內存單元地址。而在有的機器上va_list是被定義成void*的 
二、定義_INTSIZEOF(n)主要是爲了某些需要內存的對齊的系統.這個宏的目的是爲了將n的長度化爲int長度的整數倍。
好比:n爲5,二進制就是101b。int長度爲4,二進制爲100b。那麼n化爲int長度的整數倍就應該爲8。

三、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的描寫敘述就是這些了,咱們要注意的 是不一樣的操做系統和硬件平臺的定義有些不一樣,但原理倒是類似的. 

相關文章
相關標籤/搜索