C語言函數可變長度參數剖析

C語言中的不少函數的入參被定義爲可變參數,最典型的函數

int printf (const char * fmt, ...)spa

要對其中的可變參數進行處理,就要用到va_list類型和 VA_START, VA_END, VA_ARG 宏 ,須要包含<stdarg.h>頭文件設計

 

利用va族函數對不定參數進行解析的過程所示以下:指針

 1 int my_printf(const char * fmt, ...)
 2 {
 3     va_list struAp;
 4     va_start(struAp, fmt);
 5 
 6     for (; *fmt; ++fmt)
 7     {
 8         if(*fmt != '%')
 9         {
10             PUTC(*fmt); 
11             continue;
12         }
13         
14         fmt++;
15         
16         switch (*fmt)
17         {
18             case 'd':
19             {
20                 int i = va_arg(struAp,int);
21                 PUTC(i);
22             }
23             break;
24 
25             default:
26                 break;
27         }
28     }
29     
30     va_end(struAp);
31 }

 

要了解不定參數的處理方式,就要搞清楚va族函數的實現code

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

 

/* 實質就是一個char型的指針 */字符串

typedef char * va_list;原型

/* 將指針偏移一個v的長度,指向後面的地址 */it

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )io

/* 根據參數類型,取出後面的數據,強制類型轉換 */

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

/* 將指針置爲NULL */

#define va_end(ap) ( ap = (va_list)0 )

 

經過解析fmt字符串,獲得後面的參數類型和個數,根據參數類型再加上偏移量就能夠找到棧中的不定參數了

 

 函數調用和傳參的過程所示以下:

將函數參數與函數調用後下一條指令的地址都壓入棧中,而後跳到函數的入口地址。

例如

void func(int param1, double param2,int param3){ }

int main()
{ 
    func(3, 1.2, 4); 
    printf("Over\n"); //設指令地址爲0x1234
    return 0;
}

 

執行f(3, 1.2, 4)的函數調用,進入func函數時的堆棧以下:

 
 這樣,經過param1的地址就能夠計算出param2與param3的地址:
 
使用不定參函數的注意事項
 
  • 在C語言中,調用一個可變參數函數時,調用者會對每一個參數執行「默認實際參數提高(default argument promotions)」

     ——float類型的實際參數將提高到double
  ——char類型的實際參數將提高到int
  ——short類型的實際參數將提高到int

  • 在沒有函數原型的狀況下,char與short類型都將被轉換爲int類型,float類型將被轉換爲double類型。

             ——《C語言程序設計》第2版  2.7 類型轉換 p36

  • 這樣寫確定是不對的:

    c = va_arg(ap,char);

  由於咱們沒法傳遞一個char類型參數,若是傳遞了,它將會被自動轉化爲int類型。上面的式子應該寫成:
  c = va_arg(ap,int);
                ——《C陷阱與缺陷》p164
相關文章
相關標籤/搜索