弱型別?C語言引數提公升帶來的乙個陷阱

2021-06-08 21:31:57 字數 2496 閱讀 3947

本文**:

先拋開我遇到的問題不說,簡單回顧下c語言的隱式型別轉換,如下一段**:

#include 

double sum(

double x,

double y)

;//宣告乙個函式

int main(

)

沒問題,我們得到的結果是20.000000。

同時,上面**中,首先宣告了sum函式的原型「double sum(double x,double y);」。 熟悉c的人都知道,在「old style c」中,我們可以宣告乙個不帶引數原型的函式,也就是這麼宣告「double sum();」 這表示sum函式可能有無限個引數,具體由函式定義來決定。那麼,如果在上述**中使用這種方式會怎麼樣呢?

double sum();

//宣告乙個函式

int main(

)

由於各個編譯器的具體實現不同,我的實驗環境是linux+gcc4.4.5。在這樣的編譯環境下,你會發現程式得到了乙個不確定的值!

很顯然,這段**跳入了乙個陷阱。即時你在編譯的時候加入了-wall選項,也不會出現任何的警告!c既然允許我們宣告乙個引數未定義的函式原型,卻為什麼不讓我們得到乙個正確的程式呢?

讓我們來看c99標準中section 6.5.2.2 "function calls" 的paragraphs 6, 7:

paragraphs6

1、預設引數提公升

:如果乙個函式的形參型別未知,那麼呼叫函式時要對相應的實參做「整數提公升(integer promotions)」,除此以外,float型別的引數會被提公升為double。

2、如果形參和實參個數不相等的時候,行為未定義;

3、 如果函式定義的時候指定引數原型,那麼a)引數原型包含(...),即變參;b)形參型別和實參型別不符合。這兩種行為都是未定義的;

4、 如果函式定義的時候不指定引數原型,如果提公升後的實參型別和形參型別不相符,則行為未定義;除了兩種情況a)提公升後乙個是unsigned int乙個是signed int,則值可以被表示成這兩種的任何形式;b)實參或形參都是指標,分別指向限定和非限定(如const)的char或者void。

paragraphs7

1、 如果乙個函式的形參型別已知,則實參的型別會被隱式的轉換成形參的型別,並且轉換成非限定的對應型別;

2、如果函式原型中有(...)引數,那麼對應的實參會被進行預設引數提公升;

(以上純屬個人翻譯,如想看原文,請參照c99標準手冊)

你也許會稍許有些疑惑,什麼情況下函式定義會沒有原型(引數型別未知)呢?讓我們來看一下:

首先,無論函式宣告還是函式定義,都是可以沒有原型的。如這樣的**:

1

2

void func(

int a,

char b,

float c)

;void func(

int a,

char b,

float c)

line1是帶引數原型的函式宣告;line2是帶引數原型的函式定義。

再看這樣的**:

123

456

void func();

void func(a, b, c)

int a;

char b;

float c;

line1是不帶引數原型的函式宣告;line2往後是不帶引數原型的函式定義 (k&r c style)。

簡單來說,以上標準可以歸結為兩點:

1、對於有引數原型的函式(非變參),實參會被隱式轉換成相應的實參的型別;

2、對於沒有引數原型的函式或者變參函式,實參會被進行「預設引數提公升」

那現在來看,我們之前的問題出再**了?首先,sum函式宣告的時候沒有定義引數原型,因此main函式在呼叫sum的時候,對引數作了int型別的提公升,而不是隱式的轉換;其次,sum函式在定義的時候指定了引數的型別,其型別同提公升後得到的int型別不相同,命中標準中paragraphs6的第三條,得到乙個未定義的行為,至於怎麼處理,那就是編譯器的事情了。

c語言的型別轉換,除了上述的這些標準,還涉及到很多很複雜的事情,比如有符號、無符號、浮點等,每一種型別轉換都要定義一種轉換規則,而且不同的編譯器不同的體系結構往往會帶來不同的結果,很多態別轉換都是c標準中未定義的,很可能就導致錯誤的出現。比如上述的這個例子,很顯然是乙個很陰暗的用法,我們在實際寫程式的時候往往不會這麼用,但是很不巧,我的乙個程式中的乙個函式引數很多,我在宣告該函式的時候就偷懶沒有指定引數的原型,而恰巧其中乙個引數被作了提公升而型別同函式定義的不一樣,導致程式出錯。因此,在c語言中,我們要盡量的避免使用隱式的型別轉換。

此外,對於文章開頭提到的「c語言是弱型別」,實在不敢苟同。其實爭論乙個語言是強弱型別本身就沒有意義,但我個人認為,c語言這種在編譯時就已經確定了資料型別的語言,如果硬要劃分的話,怎麼也不應該算是乙個弱型別!

參考數目:

c和c++經典著作:c陷阱與缺陷

你必須知道的495個c語言問題

C語言裡的型別提公升

一 型別的提公升 把char unsigned char short unsigned short轉換成int型別稱為型別提公升 promotion 1.如果short的位元組長度小於int的位元組長度 char轉換成 int unsigned char轉換成 int short轉換成 int un...

乙個問題帶來的學習

故事 從前,有乙個地主,他擁有很多座糧倉,分別儲存了不同單位的糧食,分布在該城鎮的不同地點,短時間內也難以再操作。有一天,山大王殺到他家裡,要挾道 我知道你有好幾座糧倉,現在我正在招兵買馬,糧草不足,您老救濟一下吧,交出幾座糧倉,保您老身體健康,發財大吉啊!要過這個冬天,弟兄們算了下需要30單位的糧...

乙個可變引數型別檢查的示例

最近在修正 警告,發現封裝的列印語句不會進行引數的型別檢查,而用printf卻是可以的。於是上網找了下資料,學習了一下。對於函式引數型別的檢查,在gcc環境中可以使用 attribute format printf,n,m 的形式。其中n表示第幾個引數是格式化字串,m指明從第幾個引數開始做檢查。對於...