1 c編譯器怎麼確定數值常量的類型
首先明確一下這裏所說的數值常量指的是程序中的一個數字,比如:
if (1 < 2)
{
}
其中1
、2
就是數值常量。
就以數字1
爲例,1
在很多類型的表示範圍之內,比如short
、int
、unsigned int
等,那麼編譯器會將1
解讀成哪個類型呢?這是由c標準規定的,對於不同版本的c標準,相關規定的具體內容有所差異,對於c90,相關規定如下表所示:
範圍 | 類型 |
---|---|
0~231-1 | int |
231~232-1 | unsigned int |
232~263-1 | long long |
263~264-1 | unsigned long long |
而對於c99,相關標準有了些變化:
範圍 | 類型 |
---|---|
0~231-1 | int |
231~263-1 | long long |
263~264-1 | unsigned long long |
細心的同學可能會發現,上表中沒有給出負值的情況,比如-10
。編譯器在遇到負值的時候,先不看符號,而是根據數字來確定類型,然後再處理負號。仍舊拿-10
打比方,編譯器根據10
來確定類型爲int
,然後再處理負號。
2 c語言數值常量的陷阱
爲什麼要強調數值常量的類型呢?因爲這裏面有一些陷阱需要注意。以c90標準來說,考慮如下的表達式:
-2147483648 < 2147483647
很多人第一反應都是這個表達式爲true
,畢竟從數值上看這是顯然的。但是,支持c90標準的編譯器可不這麼認爲!關鍵是-2147483648
,編譯器根據2147483648
也就是231,確定這個數值的類型爲unsigned int
。再考慮負號,根據負數的補碼的轉換規則:對應正數(絕對值)按位取反再加1:
8000_0000 按位取反⇒ 7fff_ffff 再加1⇒ 8000_0000
就這樣,編譯器把-2147483648
硬是給處理成了unsigned int
類型的2147483648
,而2147483648
是大於2147483647
的。但是編譯器又不能把2147483648
解讀成int
類型,因爲補碼錶示是不對稱的,int
類型最小可以表示到-2147483648
,但最大隻能表示到2147483647
。如果能把2147483648
表示成更大的有符號類型比如long long
就不會有上述問題了,c99標準正是這麼做的。不過爲了寫出不同版本的標準下,行爲一致的程序,我們還是要注意這個細節。
爲了加深理解,不妨再來看幾個例子(仍然根據c90標準):
例1
int i = -2147483648;
if (i < 2147483647)
{
printf("-2147483648 < 2147483647\n")
}
此時,會打印出-2147483648 < 2147483647。上文已說過,編譯器會把-2147483648
解讀成一串二進制0x8000_0000 ,因此變量i所在內存存儲的也是這一串二進制,但不同的是,因爲i是int
類型,因此會以int
來看待這串二進制,一個負數自然是小於2147483647的。
例2
-2147483647 - 1 < 2147483647
對於-2147483647
,編譯器根據2147483647
確定其爲int
類型,然後處理負號:
7fff_ffff 按位取反⇒ 8000_0000 再加1⇒ 8000_0001
-2147483647 - 1
則得到8000_0001 - 1 = 8000_0000
,由於是按int
類型來看,因此8000_0000
是一個負數,且是int
所能表達的最小負數-2147483648
。所以例2的表達式爲true
。值得一提的是,INT_MIN通常就定義爲:
#define INT_MIN (-2147483647 - 1)
這樣一來,這個宏不管在c90還是c99標準都能工作的很好。
例3
if (-2147483648 - 1 == 2147483647)
{
printf("-2147483648 - 1 == 2147483647\n")
}
編譯器會將-2147483648
解讀成unsigned int
類型的二進制串8000_0000
,而8000_0000 - 1 = 7fff_ffff
,因此例3中條件成立,會執行printf語句。
3 有符號數和無符號數在一起運算
除了讓編譯器自己去解讀數值常量的類型,我們還可以通過在數值常量後面加上後綴來主動告訴編譯器數值常量的類型。比如0U
或0u
就是告訴編譯器,這是一個unsigned int
的0
。此外,也可以用強制類型轉換來實現這一目標,比如(unsigned)0
。
值得關注的是,當無符號數和有符號數一起運算時,c語言會進行類型提升,把有符號數按照無符號數看待。不同類型的數混在一起運算時,有一些值得注意的地方,如下表:
關係表達式 | 類型 | 結果 | 說明 |
---|---|---|---|
0 == 0U | 無符號 | true | 0000_0000 == 0000_0000 |
-1 < 0 | 有符號 | true | -1 < 0 |
-1 < 0U |
無符號 |
false |
ffff_ffff > 0000_0000 |
2147483647 > -2147483647 - 1 | 有符號 | true | 231-1 > -231 |
2147483647U > -2147483647 - 1 |
無符號 |
false |
231-1 < 231 |
2147483647 > (int)2147483648U |
有符號 |
true |
231-1 > -231 |
-1 > -2 | 有符號 | true | -1 > -2 |
(unsigned)-1 > -2 | 無符號 | true | 231-1 > 231-2 |