C可變引數的實現

jasonyuchen發表於2017-08-13
原文 >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

相關文章