為什麼printf("%ld ", -2147483648/1000000000);得到的結果是2而不是-2?
printf("%ld
得到的結果是2而不是-2但是int a = -2147483648;int b = 1000000000;
", -2147483648/1000000000);printf("%d
的結果是-2。使用gcc (Debian 4.7.2-5) 4.7.2編譯時提示warning: this decimal constant is unsigned only in ISO C90 [enabled by default]
", a/b);
對這類問題,以及類似的問題,我有這幾個建議:
1,認真看 warning 信息。
2,學好英語,至少確保足夠的閱讀能力(聽、說的能力對程序員來說遠遠沒有閱讀能力重要)3,學好 google 的使用,至少學會「拿 warning 信息當作關鍵詞去搜索」這個技能。
其它該講的,其它樓層也都講了。
補充:
這個問題實際上警告的是 C90 標準對整數常量的特別對待,會造成意料之外的結果。
要解決這個問題最簡單的方案其實是:把編譯器標準換成 C99,或者 C1x一般來說,我認為新項目可以忽略 C90 這種 二十多年前的老標準。使用 gcc -std=c99 或者 gcc -std=c1x 都不會存在這個問題。很有趣的問題。
-2147483648並不是一個數值常量,而是一個表達式,它是2147483648這個數值常量前面加了一個單目運算符「-」。而2147483648本身超出了int型的範圍,而你又沒有用後綴(L,UL等)指明它的類型,所以會導致undefined behavior。下面是我想到的,-2147483648這個表達式的幾種可能的求值過程:
1) 2147483648超出了int型的範圍,溢出後變成了int型的-2147483648。對這個值求相反數,結果2147483648又超出了int型的範圍,又溢出變成了int型的-2147483648。結果恰好對了。2) 2147483648超出了int型的範圍,被cast成了unsigned int型的2147483648。對這個值求相反數,結果-2147483648又超出了unsigned int型的範圍,又溢出變成了unsigned int型的2147483648。結果差了一個負號。3) 2147483648超出了int型的範圍,被cast成了long int型(64位)的2147483648。對這個值求相反數,結果-2147483648仍在long int型的範圍內。結果恰好對了。上面3種情況都只是「可能」的,具體是哪一種依賴於編譯器。作為程序員,不應該依賴這種undefined behavior。再回到你的程序。
4) int a = -2147483648; 這一句,無論-2147483648經過了上面3種求值過程中的哪一種,最後的結果都會被cast成int型賦給a。這樣,a的值一定是-2147483648。所以a/b的計算結果是對的。5) 而printf("%ld", -2147483648/1000000000); 這一句,從你的編譯器輸出(enabled)來看,應該是把2147483648當成了unsigned int型常量,即採用了上面第2種求值過程。於是結果就反了。
Bonus:
既然-2147483648這個表達式會導致undefined behavior,那麼當程序中真的要用到-2147483648這個值的時候,應該怎麼辦呢?有兩種辦法:a) 寫成(int)-2147483648。由上面(4)的分析,cast成int型之後值一定是負的。不過,這樣做雖然結果正確,但過程中畢竟引發了undefined behavior,並不推薦。
b) 使用INT_MIN。這個宏的定義如下:#define INT_MIN (-2147483647 - 1)這個表達式不會引發undefined behavior,且結果確實是-2147483648。參考:(-2147483648&> 0) returns true in C++?Why it is different between -2147483648 and (int)-2147483648-2147483648/1000000000出錯的原因是 / 左邊得到的是無符號(負)數,/ 右邊是有符號數。
1. 把你的程序中%ld改為%d,然後編譯運行。 -std=c89 是默認值,不寫也可以,-m32表示生成32位機器的代碼,我的系統是64位的, long int 的最大值大於214748367。根據warning信息,可以知道 -2147483648/1000000000 經編譯器評估後類型是 long unsigned int 而不是 signed int。據此推測 -2147483648經編譯器評估後的類型為 long unsigned int。? /tmp cat -n c.c
1 #include &
2 int main(){
3 printf("%d
", -2147483648/1000000000);
4 }
? /tmp gcc -m32 -std=c89 c.c
c.c: In function 『main』:
c.c:3:2: warning: this decimal constant is unsigned only in ISO C90 [enabled by default]
c.c:3:2: warning: format 『%d』 expects argument of type 『int』, but argument 2 has type 『long unsigned int』 [-Wformat]
? /tmp ./a.out
2
? /tmp
2. 把 -2147483648 賦給long signed 類型的 s 後,s 的值變為2147483648。所以 -2147483648/1000000000 的值是2。把 s 賦給long unsigned 類型的 aa 後,只要按照正確的方式解讀 aa 變數中的二進位數,就能輸出正確的值(aa(lu) 和 s(lu) 相同)。這涉及到數的二進位表示,正負數的原碼補碼錶示,這些我不熟,想了解的自己查wikipedia吧。
? /tmp cat -n unsigned.c
1 #include &
2
3 int main(){
4 long unsigned a = -2147483648/1000000000;
5 long signed s = -2147483648;
6 long unsigned aa = s;
7 printf("a(lu) = %lu
", a);
8 printf("aa(ld) = %ld, aa(lu) = %lu, s(lu) = %lu
", aa, aa, s);
9 }
? /tmp gcc -m32 -std=c89 unsigned.c
unsigned.c: In function 『main』:
unsigned.c:4:2: warning: this decimal constant is unsigned only in ISO C90 [enabled by default]
unsigned.c:5:2: warning: this decimal constant is unsigned only in ISO C90 [enabled by default]
? /tmp ./a.out
a(lu) = 2
aa(ld) = -2147483648, aa(lu) = 2147483648, s(lu) = 2147483648
? /tmp
printf("%ld
", ((long signed)-2147483648)/1000000000);
再編譯,就會得到正確的 -2 了。
C90 標準中說,沒有後綴的數學常量的類型依次 int, long int, unsigned long int,直到這個類型可以表示這個數。C99 的類型列表則是 int, long int, long long int,所以上面的代碼換成 -std=c99 也沒有問題。至於為什麼 -2147483648 在C90里會被評估為 unsigned long int,根據這裡 Warning: this decimal constant is unsigned only in ISO C90 ,是因為編譯過程中 -2147483648由 2147483648 取反得到,而 2147483648 超出了 int, long int的範圍,所以被提升為 unsigned long int(你的機器上int 和 long int的範圍是一樣的),這也解釋了 warning: this decimal constant is unsigned only in ISO C90。
根據 Richard Earnshaw ,C90隻有正的常量,負的常量通過正的常量取反得到。所以在這個過程中-2147483648的類型就要被提升為unsigned long int了。
另一個參考:Can"t get rid of "this decimal constant is unsigned only in ISO C90" warning這兩個 stackoverflow 的鏈接是 google "warning: this decimal constant is unsigned only in ISO C90" 搜索結果的前兩條。善用google,而且一定使用英文的錯誤提示作為關鍵詞,這樣才能搜索到英文的內容,英文世界比中文世界豐富多了。跑題一下…解決一個編譯期錯誤,比解決一個運行時錯誤成本要低的多。很多編譯警告,將來可能就會成為一個運行時錯誤。連編譯警告都不認真對付的人,很難相信他會對自己的代碼仔細審查。
一直不明白怎麼這麼多人不拿警告當回事…
大概是因為徹底了解一個警告的含義,需要很多背景知識。既然編譯器都認為這是一個合乎語法但存在其他隱患的點,那麼它往往涉及了一些模糊的灰色地帶。利用這個機會懂得更多細節,真是個上佳的學習機會。關於計算機系統內部數的表達方式,尤其是浮點,我覺得最好的書是《深入理解計算機系統》,中譯本可能是%ld用的不對吧,我不知道你怎麼編譯成功的,我 gcc 4.8.1 clang 3.2 ubuntu 系統
推薦閱讀:
※在計算機語言發展歷史上,C語言和C++語言分別有怎樣的歷史意義?
※為什麼C語言中2個無符號數相減會得到負數?
※c++中有些重載運算符為什麼要返回引用?
※為什麼C語言考試不夠好?
TAG:C編程語言 |