可變引數函式定義及其陷井

2022-03-12 09:00:50 字數 4113 閱讀 9622

一、關於可變引數的函式定義方法

某些情況下希望函式的引數個數可以根據需要確定。典型的例子有大家熟悉的函式printf()、scanf()和系統呼叫execl()等。那麼它們是怎樣實現的呢?c編譯器通常提供了一系列處理這種情況的巨集,以遮蔽不同的硬體平台造成的差異,增加程式的可移植性。這些巨集包括va_start、 va_arg和va_end等。 

1. 採用ansi標準形式時,引數個數可變的函式的原型宣告是:

view plain

copy to clipboard

print

?type funcname(type para1, type para2, ...)   

這種形式至少需要乙個普通的形式引數,後面的省略號不表示省略,而是函式原型的一部分。type是函式返回值和形式引數的型別。

2. 採用與unix system v相容的宣告方式時,引數個數可變的函式原型是:

view plain

copy to clipboard

print

?type funcname(va_alist)  

va_dcl  

這種形式不需要提供任何普通的形式引數。type是函式返回值的型別。va_dcl是對函式原型宣告中引數va_alist的詳細宣告,實際是乙個巨集定義,對不同的硬體平台採用不同的型別來定義,但在最後都包括了乙個分號。因此va_dcl後不再需要加上分號了。va_dcl在**中必須原樣給出。 va_alist在vc中可以原樣給出,也可以略去。

此外,採用標頭檔案stdarg.h編寫的程式是符合ansi標準的,可以在各種作業系統和硬體上執行;而採用標頭檔案varargs.h的方式僅僅是為了與以前的程式相容。所以建議大家使用前者。以下主要就前一種方式對引數的處理做出說明。兩種方式的基本原理是一致的,只是在語法形式上有一些細微的區別。

view plain

copy to clipboard

print

?/* 取第乙個可變引數的指標給arg_ptr

last_firm_arg是函式宣告中最後乙個固定引數,該

巨集參方便編譯器定位第乙個可變引數的位址,因為在函式

呼叫的棧結構中,可變引數總是在因定引數的後面 */

void

va_start( 

va_list

arg_ptr, last_firm_arg );  

/* 返回arg_ptr指定的當前可變引數的值,然後arg_ptr指向下一引數

cur_arg_type是當前引數的型別,如int,該

注意支援的型別為int和double,這是乙個陷井,下文將詳述

可迴圈呼叫此巨集得到n個引數值 */

cur_arg_type va_arg( va_list

arg_ptr, cur_arg_type );  

/* arg_ptr置為null */

void

va_end( 

va_list

arg_ptr );  

va_start使argp指向第乙個可選引數。va_arg返回引數列表中的當前引數並使argp指向引數列表中的下乙個引數。va_end把argp指標清為null。函式體內可以多次遍歷這些引數,但是都必須以va_start開始,並以va_end結尾。  

呼叫者在實際呼叫引數個數可變的函式時,要通過一定的方法指明實際引數的個數(編註:實際上,每個引數的資料型別(占用位元組數)也需要以一定的方法指明,如採用預設型別或以固參指明型別,printf()的首參——格式化字串中的型別格式符%d、%f、%s等就是顯式指明的),例如把最後乙個引數置為空字串(系統呼叫execl()就是這樣的)、-1或其他的方式(函式printf()就是通過第乙個引數,即輸出格式的定義來確定實際引數的個數的)。  

下面給出乙個具體的例子。是採用了符合ansi標準的形式的**。**中加了一些注釋,這裡就不再解釋了。該例子已經在vc/windows xp、cc/aix4.3.2.0、gcc/suse7.3環境下編譯並正常執行。

演示如何使用引數個數可變的函式,採用ansi標準形式

view plain

copy to clipboard

print

?#include < stdio.h >;  

#include < string.h >;  

#include < stdarg.h >;  

/* 函式原型宣告,至少需要乙個確定的引數,  注意括號內的省略號 */

intdemo( 

char

*, ... );    

void

main( 

void

)    

/* ansi標準形式的宣告方式,括號內的省略號表示可選引數 */

intdemo( 

char

*msg, ... )    

va_end( argp ); /* 將argp置為null */

return

0;    

}二、可變參型別陷井

下面的**是錯誤的,執行時得不到預期的結果:

view plain

copy to clipboard

print

?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

可變引數函式定義及其陷井

一 關於可變引數的函式定義方法 某些情況下希望函式的引數個數可以根據需要確定。典型的例子有大家熟悉的函式printf scanf 和系統呼叫execl 等。那麼它們是怎樣實現的呢?c編譯器通常提供了一系列處理這種情況的巨集,以遮蔽不同的硬體平台造成的差異,增加程式的可移植性。這些巨集包括va sta...

Python函式可變引數定義及其引數傳遞方式詳解

python函式可變引數定義及其引數傳遞方式詳解 python中 函式不定引數的定義形式如下 1 func args 傳入的引數為以元組形式存在args中,如 def func args print args func 1,2,3 1,2,3 func 1,2,3 這個方式可以直接將乙個列表的所有元...

詳解Python函式可變引數定義及其引數傳遞方式

python函式可變引數定義及其引數傳遞方式詳解 python中 函式不定引數的定義形式如下 1 func args 傳入的引數為以元組形式存在args中,如 def func args print args func 1,2,3 1,2,3 程式設計客棧gt func 1,2,3 這個方式可以直接...