C可變參數的實現

原文 >http://blog.csdn.net/weiwangchao_/article/details/4857567

  C函數要在程序中用到以下這些宏:

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_list:用來保存宏va_start、va_arg和va_end所需信息的一種類型。爲了訪問變長參數列表中的參數,必須聲明
             va_list類型的一個對象       定義: typedef char *  va_list;
va_start:訪問變長參數列表中的參數之前使用的宏,它初始化用va_list聲明的對象,初始化結果供宏va_arg和
               va_end使用;
va_arg: 展開成一個表達式的宏,該表達式具有變長參數列表中下一個參數的值和類型。每次調用va_arg都會修改
              用va_list聲明的對象,從而使該對象指向參數列表中的下一個參數;
va_end:該宏使程序能夠從變長參數列表用宏va_start引用的函數中正常返回。
va在這裏是variable-argument(可變參數)的意思.
這些宏定義在stdarg.h中,所以用到可變參數的程序應該包含這個頭文件.下面我們寫一個簡單的可變參數的函數,改函數至少有一個整數參數,第二個參數也是整數,是可選的.函數只是打印這兩個參數的值.

  1. #include <stdio.h>;    
  2. #include <string.h>;    
  3. #include <stdarg.h>;    
  4.   
  5. /* ANSI標準形式的聲明方式,括號內的省略號表示可選參數 */    
  6.   
  7. int demo(char *msg, … )    
  8. {    
  9.     va_list argp;                   /* 定義保存函數參數的結構 */    
  10.     int argno = 0;                  /* 紀錄參數個數 */    
  11.     char *para;                     /* 存放取出的字符串參數 */    
  12.       
  13.                                     /* argp指向傳入的第一個可選參數,    msg是最後一個確定的參數 */    
  14.     va_start( argp, msg );    
  15.       
  16.     while (1)   
  17.     {    
  18.         para = va_arg( argp, char *);                 /*    取出當前的參數,類型爲char *. */    
  19.         if ( strcmp( para, “/0”) == 0 )    
  20.                                                       /* 採用空串指示參數輸入結束 */    
  21.             break;    
  22.         printf(”Parameter #%d is: %s/n”, argno, para);    
  23.         argno++;    
  24.     }    
  25.     va_end( argp );                                   /* 將argp置爲NULL */    
  26.     return 0;    
  27. }  
  28.   
  29.   
  30. void main( void )    
  31. {    
  32. demo(”DEMO”“This”“is”“a”“demo!” ,“333333”“/0”);    
  33.   
  34.   
  35. }    

 

從這個函數的實現可以看到,我們使用可變參數應該有以下步驟: 
1)首先在函數裏定義一個va_list型的變量,這裏是arg_ptr,這個變 
量是指向參數的指針. 
2)然後用va_start宏初始化變量arg_ptr,這個宏的第二個參數是第 
一個可變參數的前一個參數,是一個固定的參數. 
3)然後用va_arg返回可變的參數,並賦值給整數j. va_arg的第二個 
參數是你要返回的參數的類型,這裏是int型. 
4)最後用va_end宏結束可變參數的獲取.然後你就可以在函數裏使 
用第二個參數了.如果函數有多個可變參數的,依次調用va_arg獲 
取各個參數. 

二、可變參類型陷阱

下面的代碼是錯誤的,運行時得不到預期的結果:

view plaincopy to clipboardprint?
va_start(pArg, plotNo);  
fValue = va_arg(pArg, float);  // 類型應改爲double,不支持float  
va_end(pArg); 
va_start(pArg, plotNo);
fValue = va_arg(pArg, float);  // 類型應改爲double,不支持float
va_end(pArg);

下面列出va_arg(argp, type)宏中不支持的type:

—— char、signed char、unsigned char
—— short、unsigned short
—— signed short、short int、signed short int、unsigned short int
—— float

C語言中,調用一個不帶原型聲明的函數時,調用者會對每個參數執行“默認實際參數提升(default argument promotions)”。該規則同樣適用於可變參數函數——對可變長參數列表超出最後一個有類型聲明的形式參數之後的每一個實際參數,也將執行上述提升工作。

提升工作如下:
——float類型的實際參數將提升到double
——char、short和相應的signed、unsigned類型的實際參數提升到int
——如果int不能存儲原值,則提升到unsigned int

然後,調用者將提升後的參數傳遞給被調用者。

所以,可變參函數內是絕對無法接收到上述類型的實際參數的。


關於該陷井,C/C++著作中有以下描述:


在《c語言程序設計》對可變長參數列表的相關章節中,並沒有提到這個陷阱。但是有提到默認實際參數提升的規則:
在沒有函數原型的情況下,char與short類型都將被轉換爲int類型,float類型將被轉換爲double類型。
                ——《C語言程序設計》第2版  2.7 類型轉換 p36

在其他一些書籍中,也有提到這個規則:

事情很清楚,如果一個參數沒有聲明,編譯器就沒有信息去對它執行標準的類型檢查和轉換。
在這種情況下,一個char或short將作爲int傳遞,float將作爲double傳遞。
這些做未必是程序員所期望的。
腳註:這些都是由C語言繼承來的標準提升。
對於由省略號表示的參數,其實際參數在傳遞之前總執行這些提升(如果它們屬於需要提升的類型),將提升後的值傳遞給有關的函數。——譯者注
                ——《C++程序設計語言》第3版-特別版 7.6 p138

…… float類型的參數會自動轉換爲double類型,short或char類型的參數會自動轉換爲int類型 ……
                ——《C陷阱與缺陷》 4.4 形參、實參與返回值 p73

這裏有一個陷阱需要避免:
va_arg宏的第2個參數不能被指定爲char、short或者float類型。
因爲char和short類型的參數會被轉換爲int類型,而float類型的參數會被轉換爲double類型 ……
例如,這樣寫肯定是不對的:
c = va_arg(ap,char);
因爲我們無法傳遞一個char類型參數,如果傳遞了,它將會被自動轉化爲int類型。上面的式子應該寫成:
c = va_arg(ap,int);
                ——《C陷阱與缺陷》p164

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章