C舊式與新式函式的宣告與定義
關於C語言函式的原型宣告與函式定義,這裡涉及了許多的內容,以此文章,記錄自己所瞭解的知識,以備日後查閱,同時也幫助自己和大家瞭解這當中暗含的“陷阱”。
由於歷史的原因,C語言的函式宣告有舊式和新式之分,舊式就是K&R,而新式則是ANSI,如下圖:
現在倡導的是使用後者,而不要使用前者,對於K&R,由於存在大量舊式程式碼,為了保持相容,所以沒有被正式廢棄。
這兩者在引數傳遞的時會有所區別,在K&R中,由於函式的引數也是表示式,所以會發生型別提升,即傳遞一個短於int的整數,函式實際所接收到的是int,如果傳遞的是float,函式實際接收到的是double,在被呼叫函式的函式體內,這些值會根據函式定義時引數的宣告型別自動裁減為該型別。
你可能會感到困惑,為什麼不嫌麻煩將它們提升為更大的型別,然後又直接把它們裁減為原來的大小?之所以這樣做,原意是為了簡化編譯器—所有的東西都是同一長度。如果只固定使用幾種型別,將大大簡化引數的傳遞。所有的引數都統一為標準長度,被呼叫函式會根據需要對它們進行裁剪。
相反在ANSI中,如果使用了適當的函式原型,型別提升便不會發生,如果引數宣告為char,則實際傳遞的也是char。使用新風格的函式定義,編譯器就會假定引數是準確宣告的,於是便不進行型別提升,並據此產生程式碼。
關於宣告和定義,我們需要考慮4種情況
- K&R C函式宣告和K&R C函式定義
能夠順利呼叫,所傳遞的引數會進行型別提升 - ANSI C函式宣告(原型)和ANSI C函式定義
能夠順利呼叫,所傳遞的引數位實際引數 - ANSI C函式宣告(原型)和K&R C函式定義
如果使用一個較窄的型別就會失敗,函式呼叫時所傳遞的是實際型別,而函式期望接收的是提升後的型別 - K&R C函式宣告和ANSI C函式定義
如果使用一個較窄的型別就會失敗,函式呼叫時所傳遞的是提升後的型別,而函式期望接收的是實際型別
所以,如果為一個K&R C函式定義增加函式原型,而原型的引數列表中有一個short引數,在引數傳遞時,這個原型將導致實際傳遞給函式的就是short型別的引數,而根據函式的定義,它期望接收的是一個int型別的引數。這樣,函式從堆疊中抓取4個位元組(int)而不是2個位元組(short)。
下面展示兩種失敗情況
/* 檔案1 */
/* 舊風格的定義,但它具有原型 */
int olddef (float d, char i);
int main(void)
{
float d = 10.0;
char j = 3;
olddef (d, j);
/* 新風格的定義,但它沒有原型 */
newdef (d, j);
}
----------------------------------------------------------------
/* 檔案2 */
/* 舊風格的定義,但它具有原型 */
olddef(d , i)
float d;
char i;
{
printf ("olddef: float = %f, char = %x \n", d, i);
}
/* 新風格的定義,但它沒有原型 */
newdef(float d, char i)
{
printf ("newdef: float = %f, char = %x \n", d, i);
}
列印結果如下
所以,堅決不要在函式的宣告和定義中混用新舊兩種風格。
還有一種錯誤的用法:
int main(void)
{
union
{
double d;
float f;
}u;
u.d = 10.0;
printf ("put in a double, pull out a float f = %f \n", u.f);
u.f = 10.0;
printf ("put in a float, pull out a double d = %f \n", u.d);
}
列印的結果為: