在看《深入理解計算機系統》第二版中文版時(Computer Systems A Programmer's Perspective Second Edititon),看到48頁第二章網絡旁注中提到:
C語言中,將TMin32(32位有符號整數的最小值)寫成 -2147483647-1。為什么不簡單地寫成 -2147483648 或者 0x80000000 ?
書中提到是由於補碼表示的不對稱性和C語言轉換規則之間奇怪的交互。補碼表示不對稱性CSAPP講解的通俗易懂,但這里面涉及到什么樣的C語言轉換規則,書中卻沒有說明。
在這篇博文的寫作過程中,搜到了一些很有用的資料,請見本文末尾的參考資料一節。本文中很大部分內容都是參照 CS:APP Web Aside DATA:TMIN:
Writing TMin in C 一文來寫的,然后根據自己的理解添加了一些細節上的東西,力求能夠更加通俗易懂一些,這就是本文存在的意義。
C語言中整型常量的實際類型
首先來看一下C語言中整型常量的定義。
C99標准中 6.4.4.1 Integer constants 中提到:
An integer constant begins with a digit, but has no period or exponent part. It may have a
prefix that specifies its base and a suffix that specifies its type.
可見如果不發生溢出,整型常量的值總是非負數。如果前面出現符號,則是對整型常量使用的一元運算符,而不是整型常量的一部分。
整型常量的實際類型取決於長度、基數、后綴字母和C語言實現確定的類型表示精度。確定整數常量類型的規則比較復雜,並且在非標准C、C89和C99中是不相同的。具體規則可見<<C語言參考手冊》第五版 第二章 2.7.1 整型常量一節。對於本文中提到的場景,下面的這個整數常量的類型表就夠用了。
表一:整數常量的類型
ISO C90 | ISO C99 | ||
十進制(Decimal) | 十六進制(Hexadecimal) | 十進制(Decimal) | 十六進制(Hexadecimal) |
int
long
unsigned
unsigned long
|
int
unsigned
long
unsigned long
|
int
long
long long
|
int
unsigned
long
unsigned long
long long
unsigned long long
|
根據C語言版本和常量的格式(十進制和十六進制),常量的數據類型是從上面表格里選擇第一個最合適(能表示常量而不溢出的)的類型。
對於ISO C90,編譯器依次嘗試int 、long、 unsigned(32位機器上long跟int一樣,是32位), 最終選擇unsigned來表示。對於 2147483648 和 -2147483648,如果表示為32位的二進制數字,它們的位表示是一樣的,都是0x80000000。所以這個常量表達式(-2147483648)的數據類型為unsigned且值為 2147483648。
對於ISO C99,編譯器依次選擇 int、long、long long,最終選擇long long類型才能容納 2147483648 。用64位,可以唯一表示 2147483648 和 -2147483648,所以這個常量表達式的數據類型為long long,值為 -2147483648。
對於16進制常數 0x80000000(注意,按照C語言中整型常量的定義,這個整數常量是正數,值為2417483648),在32位機器上,編譯器也是利用同樣的規則,依照表一中的16進制的列表來處理。兩個語言標准中,都是首先跟TMax32(0x7FFFFFFF)比較,由於0x80000000更大,所以這個值不能用int來表示。接下來和UMax32(0xFFFFFFFF)比較,由於比它小一些,所以選擇unsigned來表示。所以這個常量表達式的數據類型是unsigned,值為0x80000000(或者說,是等於2147483648)。
在64位的機器上,事情稍微有些不同。兩個語言標准中,十進制的格式 -2417483648 都是long(64位)類型,值為 -2417483648,然而十六進制格式 0x80000000 都是unsigned類型,值為0x80000000(或者說,是 2147483648)。
用一句話來解釋C語言中TMin32的古怪寫法的原因:雖然-2147483648 這個數值能夠用int類
型來表示,但在C語言中卻沒法寫出對應這個數值的int類
型常量。
C語言中如何正確表示TMin32呢?
C語言中limits.h中定義了如下兩個宏:
#define INT_MAX 2147483647 #define INT_MIN (-INT_MAX - 1)
ISO C99在stdint.h文件中定義了一些宏 INTN_C、UINTN_C、INTMAX_C與UINTMAX_C,提供對整型常量的長度與類型的可移植性控制。
知道這個有什么用?:
1. 考慮如下代碼:
int dcomp = (-2147483648 < 0); int hcomp = (0x80000000 < 0);
請大家思考一下dcomp和hcomp的值是0還是1?需要考慮編譯器指定的C語言版本和機器位數(字的大小)。
gcc 中,C模式默認的C語言標准是gnu89(ISO C90, 包括一些C99特性)。可以通過 -std 選項來指定C語言的版本。
MSVC支持C90,只支持部分的C99特性。
2. 考慮如下代碼:
int dtmin = -2147483648; int dcomp2 = (dtmin < 0); int htmin = 0x80000000; int hcomp2 = (htmin < 0);
如果你親自在32位和64位機器上用ISO-C90和ISO-C99版本來編譯運行一下,會發現,所有情況下,dcomp2和hcomp2的值都是1。你能解釋一下為什么嗎?
廣告一下吧,在搜 TMin 資料的時候,發現了一本書《一站式學習C編程》,網上有第一版的html的版本,我只看了其中介紹整型 的一節,發現作者寫的通俗易懂,而且是國人寫的,必須得支持啊。
參考資料:
C語言參考手冊第五版 2.7.1 整型常量
深入理解計算機系統 2.2.3 補碼編碼
補充閱讀:
由於補碼的不對稱性,導致在用補碼來表示負數的編譯器中使用abs()函數會有問題
如果TMin32/-1,也會導致同樣的問題
如果您看了本篇博客,覺得對您有所收獲,請點擊右下角的“推薦”,讓更多人看到!

