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中,所以用到可變參數的程式應該包含這個標頭檔.下面我們寫一個簡單的可變參數的函數,改函數至少有一個整數參數,第二個參數也是整數,是可選的.函數只是列印這兩個參數的值.
#include <stdio.h>;<br />#include <string.h>;<br />#include <stdarg.h>; </p><p>/* ANSI標準形式的聲明方式,括弧內的省略符號表示選擇性參數 */ </p><p>int demo(char *msg, ... )<br />{<br />va_list argp; /* 定義儲存函數參數的結構 */<br />int argno = 0; /* 紀錄參數個數 */<br />char *para; /* 存放取出的字串參數 */ </p><p> /* argp指向傳入的第一個選擇性參數, msg是最後一個確定的參數 */<br />va_start( argp, msg ); </p><p>while (1)<br />{<br />para = va_arg( argp, char *); /* 取出當前的參數,類型為char *. */<br />if ( strcmp( para, "/0") == 0 )<br /> /* 採用空串指示參數輸入結束 */<br />break;<br />printf("Parameter #%d is: %s/n", argno, para);<br />argno++;<br />}<br />va_end( argp ); /* 將argp置為NULL */<br />return 0;<br />}</p><p>void main( void )<br />{<br />demo("DEMO", "This", "is", "a", "demo!" ,"333333", "/0"); </p><p>}
從這個函數的實現可以看到,我們使用可變參數應該有以下步驟:
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