由於一些編譯器還不支持C99,所以下述的討論範圍一般在C89或C90內。

C語言標準對整型常量的類型有這樣的描述:如果沒有後綴且是十進位形式的,那麼它的類型根據其值的大小可能為int、long、unsigned long;如果為沒有後綴且是八進位或十六進位的,根據值的大小可能為int、unsigned int、long、unsigned long。我想問,對於十進位形式的整型常量的類型為什麼不經過unsigned int,直接從int跳到long;按理說它應該像八進位與十六進位一樣,從小到大逐一遍歷選取。注意:這裡說的類型是指原來的類型,不是指在表達式中轉換提升後的類型。這個問題可以算勉強解決了:為什麼十進位形式不遍歷unsigned int,它的類型遍歷是從int、long再到unsigned long;在此總結多日來查閱參考得出的可能最主要的原因:(減少在移植時出問題)

首先,當無符號類型與長度相同的有符號類型一起運算時,其結果類型對C語言來說是無法預料的,也就是說結果的類型將有具體的實現;為什麼呢?是這樣的:

C語言規定:較長類型的整數至少要佔有較短類型整數一樣的空間,但它們可以佔有一樣大小的空間,這取決於具體的實現。因此當無符號類型與有符號類型運算時,比如,unsigned int與long int,當它們被規定為長度相同,那麼結果是什麼類型,僅從語法上來說,不能確定結果的類型,因為不知道一個unsigned int的數long int能不能裝的下,如果裝的下則在類型轉換時將unsigned int轉換成long int,結果類型為long int,裝不下則將long int、unsigned int轉換unsigned long int,結果類型為unsigned long int,所以在考慮到移植性時,應儘可能的用有符號類型int、long,這樣不管在哪種環境下,實現在語言中就可以確定其類型,而且無符號類型的表數範圍大於有符號類型,在運算中一般要將有符號類型轉換成無符號類型,而這種轉換要比反向轉換複雜的多,佔用更多的計算時間,顯然選用unsigned int的弊大於不用unsigned int,不選用unsigned int的利大於選用unsigned int。語言設計者,在考慮到C程序的空間利用率、時間效率、移植性,作出一個好的折中(在設計領域有句話:一個好的設計取決於一個好的折中):在盡量減少空間浪費的情況下,提高程序的運行效率和通用性。而這兩點對於任何程序來說都是衡量的最主要標準。而空間可以在物理上擴大,這是必然趨勢,效率與通用性則只能在程序中提高,雖然也可以提供更快的處理器,但這顯然是種魯莽的想法。對了,最後的unsigned long是在不得已的情況下選取的,因為在C99以前沒有提供比它更大的整數類型。最後,感謝本問題所有的回答者、評論者,你們的回答與意見很關鍵,至少是啟發性的、參考性的。本問題已求解。在上述有關unsigend int與long int的轉換例子中:當long int不能裝下unsigend int時,是同時將兩個操作數都轉換為unsigend long int,現已改正,原本錯誤為unsigend int。在此對「薛非」指出的錯誤的誤解說聲抱歉!對閱讀者的誤導更是慚愧!

「無符號數」與「無符號類型」,「有符號數」與「有符號類型」,如Zerg Will所述的確是不同概念,現已在答案中一律更正為「類型」。

對於語言引起的混亂之處說聲抱歉!感謝Zerg Will提出的問題!歡迎參與者能夠指出更多的問題,或者將答案補充的更加完善,這樣以後有同樣問題的人就可以不在這種問題上再多花精力查找資料尋求解答。


更新

簡單來說,如果在int, long long之間再插入unsigned int,那麼當我們寫long long v = -2147483648時(注意-2147483648落在64位long long的合法範圍內),會發現v的值其實是2147483648,這顯然違背語義(想像一下如果我們寫int v = -1結果發現v的值是1時會怎麼噴Bjarne,逃

-- 先前內容 --

Po主關於oct和hex的答案是對的,但其餘部分感覺有些問題(也許是我理解錯了,請多多指教),我提一些(原諒我手頭暫無C編譯器,所以下面就先用C++來說明了):

1. Integer和Literal

Po主提到了「有符號數」和「無符號數」,這裡有個誤區:integer和integer literal不是一回事,所有的integer literal都是沒有負號、正號這種東西的。具體來說,如果你寫-1,在編譯器眼裡是一個「-」和一個decimal integer literal "1";而不是"-1"作為一個decimal integer literal。

2. Integer Literal Type

事實上,正確的integer literal type應該使用signed類型,否則會出現坑爹的結果。為啥?上一點已經說了,integer literal是無所謂符號的,所以如果類型順序是int(32位), unsigned int(32位), long(64位)這樣的順序,而integer literal恰好大於int小於unsigned int,那麼這個integer literal就會被解釋為unsigned int。那如果這個integer literal前面還有個負號,結果是不是就煞筆掉了?舉個例子。因為decimal在C++11下已經不會被解釋為unsigned了(和C99類似),所以這裡就以hex為例。以下:

std::cout &

在int是32位,long long是64位的新編譯器上的輸出結果會是:

134217728

-1342177282147483648214748364834359738368-34359738368

第四個輸出結果,我沒有打漏負號哦!為啥會出現這麼不科學的結果啊?剛才說了,因為這詭異的integer literal恰好大於int,但又小於unsigned int,所以就被詭異地解釋成unsigned int了,然後前面又加了個負號(oh shit)……注意上述6個值都落在long long範圍內,這意味著當我們寫long long v = xx時,即便xx落在long long範圍內v的值也不一定是xx(卧槽標準委員會你趕緊把unsigned都給我扔掉!)……

如果乖乖用C++11下的decimal呢?

std::cout &

在int是32位,long long是64位的新編譯器上的輸出結果會是:

-134217728

-2147483648-34359738368你看不牽涉到unsigned的decimal的結果多科學呀(註:在VS2013上第二個結果會有問題,因為出於兼容性它沿用了C89;g++或clang++不開啟C++11也會有問題)!

所以說嘛,C89在int, long之後補上unsigned long並不是個明智的做法。當然那時候沒有long long,所以沒啥問題。但這明顯影響了擴展性啊對不。事實上,C99出來時有一篇paper吐槽過這個地方,具體名稱我忘了,大概意思是這樣的:

4000000000 &> -1在C89會解釋為false,在C99會解釋為true,which one do you prefer?

更糟糕的是,現在的標準不僅允許使用int, long和long long,還允許使用擴展類型:

If an integer literal cannot be represented by any type in its list and an extended integer type (3.9.1) can represent its value, it may have that extended integer type. If all of the types in the list for the literal are signed, the extended integer type shall be signed. If all of the types in the list for the literal are unsigned, the extended integer type shall be unsigned. If the list contains both signed and unsigned types, the extended integer type may be signed or unsigned.

上文來自C++11。要是現行標準用unsigned類型封頂,那在支持擴展類型的機器和不支持擴展類型的機器上就會出現完全不同的結果。

總結

Po主對oct和hex的看法基本是正確的,oct和hex之所以要允許unsigned,是因為使用它們的人很可能假定它們是unsigned的,然後就能去搞位運算什麼的,所以標準就留著unsigned。但對於常用的decimal,支持unsigned只是在純添亂。PS.我對C/C++的認識只停留在大一水平,在大一學了這兩門語言後我基本沒再碰過它們,所以如果上面的看法完全錯誤,那也很正常(對此我只能請求各位看客的原諒,浪費你們的時間了)。還是請大牛 @vczh 來檢查下吧,求輕噴,謝謝!

這個問題很有意思啊,提問者的問題是:

對於十進位的為什麼不經過unsigned int,直接從int跳到long;按理說它應該像八進位與十六進位一樣,從小到大逐一選取。

其實這個是不對的,因為不經過unsigned int才是正常的,反之八進位和十六進位表達式沒有跳過這個才是不正常的。

這是因為我們用16進位或者8進位的時候,更多是為了位運算,例如int是16位的,那麼0xFFFF就用int裝不下,這樣就會變成32位的long,變成0x0000FFFF,很顯然這是一種浪費,因為位運算根本用不著這麼多位,譬如說我們要把x的高八位和y的低八位組合在一起,一般是這樣寫:

(x0xFF00)|(y0xFF)

因為int是16位的,就裝不下0xFF00的正數,就必須升位成long,結果就會變成:

(x0x0000FF00)|(y0xFF)最後得到的結果就莫名其妙變成了long型的。

最終答案已在描述中公布,這裡做幾點補充:

那麼八進位與十六進位為什麼要選擇呢?理由很簡單:因為這兩種形式的整型常量能夠很好的與二進位數對應,在一些對「位」需要嚴格控制的場合就會很方便,可以說它提供了對「位」細微的操作,因此保留unsigned int是很有必要的。

至於有人會問,為什麼不從short int開始呢?是這樣的:

int的空間長度在C語言規定是機器最為高效的位數,所以不得已,一般能夠用int當然要用int。這一點是在實際代碼中可以得到驗證的。而且short int以及枚舉常量、字元常量等在運算中會進行整型提升為int或unsigned int。

C語言標準對整型常量的類型有這樣的描述:如果沒有後綴且是十進位形式的,那麼它的類型根據其值的大小可能為int、long、unsigned long;

據我所知,C語言的最新標準不是這樣規定的

樓主所說的標準是哪個?C90?

C11的規定是intlonglong long

因此當無符號數與有符號數運算時,比如,unsigned int與long int,當它們被規定為長度相同,那麼結果是什麼類型,僅從語法上來說,不能確定結果的類型,因為不知道一個unsigned int的數long int能不能裝的下,如果裝的下則在類型轉換時將unsigned int轉換成long int,結果類型為long int,裝不下則將long int轉換unsigned int,結果類型為unsigned int

這個說法同樣是錯誤的。

因為如果long表示不了那個unsigned操作數時,結果類型並不是unsigned int,而是unsigned long


題主雖然說問題已解答,但是答案感覺有點牽強,我來說說我的想法。

題目中問的是整形常量的類型,各位的答案都有些跑偏了,看來有必要重申一下什麼是整形常量。

int a =100;

int b = a * 2;

100是整形常量,2也是整形常量,所以討論應該限定在這個範圍內。

在討論前,我用 Dev-C++ 5.6.0 (操作系統是 WIN7 64bit 家庭版)做了一些實驗:

準備: int 類型 4 byte; long 類型 4 byte; unsigned int 類型 4 byte; unsigned long 類型 4 byte; long long 類型 8 byte;

int 類型值的範圍:-2147483648~2147483647

unsigned int 類型值的範圍:0~4294967295

實驗1:

int a = 4294967295; printf("a:%d
",a);結果: a:-1

實驗2:

int a = 4294967295; printf("a:%u
",a);結果: a:4294967295

實驗3:

unsigned int a = 4294967295; printf("a:%d
",a);結果: a:-1

實驗4:

unsigned int a = 4294967295; printf("a:%u
",a);結果: a:4294967295

實驗5:

int a = -1; printf("a:%u
,a);結果: a:4294967295

以上5個實驗說明了無論是 int 還是 unsigned int,它們在內存中存儲的內容都是一致的,在此我用的是 0xFFFFFFFF,使用 %d 列印出來時顯示的是 -1(說明計算機採用補碼錶示負數),使用 u% 列印出來的就是 4294967295。

以上5個實驗是為了讓大家直觀的感受 int 和 unsigned int 的關係,很明顯地可以看出,int 和 unsigned int 實質上沒有區別,在內存中可以存儲相同的內容,只是在按不同類型表示時,結果不一樣,如 %d 是按有符號十進位顯示,%u 是按無符號十進位顯示。

接下來再做一個實驗。

實驗6:

int a = 4294967296; printf("a:%u
",a);結果: a:0

實驗6說明了等式右邊的常量超出了 4 byte (0x1 0000 0000)以後,對於 a 的賦值,只截取後 4 byte 的內容。

開始結論:

做了這麼幾個實驗,相信大家心中都有一些譜了,我之所以沒有用十六進位和八進位常量來做實驗,是因為通過以上的實驗,題主的問題已經不是問題了,也就是說不用再糾結整型常量是什麼類型了。

為什麼呢?

因為問題的關鍵不在於整型常量是什麼類型,關鍵在於賦值給什麼樣的類型,因為不管你的整形常量是什麼類型,最後都必須轉換為被賦值變數的類型。

前5個實驗說明了 int 和 unsigned int 其實沒實質區別,所以題主中的少了一個 unsigned int 也沒什麼大不了,因為我用 int 也可以存儲 unsigned int 的內容,然後在使用時當作 unsigned int 結果也是正確的;實驗6中,4294967296 超過了 int 的 4 byte,實際上應該是 long long 類型,但是最後賦值給 int,高位的數據還是給截掉了,所以你等式右邊是什麼類型都無所謂,int 關心的只是你低 4 byte 的內容。

回答補充

----------------------------------------------------------------我大概理解你後一段話的意思:int a = 100;int b = a + 4000000000 // 在 2147483647~4294967295 之間,int 表示範圍之外,unsigned 表示範圍之內

在上面的的代碼中,4000000000 其實可以用 unsiged int 來表示,在我的編譯環境中,long 和 int 都是 4 byte,但是有些計算機 long 可能是 8byte,跳過 unsigned int ,也就是說 4000000000 會變成 long 類型,在計算中佔用了 8 byte,不僅如此,a + 4000000000 中的 a 還會被隱式轉換成 long 類型來進行運算,在賦值給 b 時運算結果又被強制轉換成 int 類型。

很可惜,我的編譯環境沒法直接給出答案,所以只能從側面驗證,如果不轉換為更高位元組的類型會怎麼樣:

1.加減法上面有提到一個小細節,就是負數是採用的是補碼。例1:unsigned int a = 2147483648; //0x80 00 00 00, 最高位是1int b = a - 1;printf("a:%d
b:%d
",a,b);結果:a:-2147483648b:2147483647

再看一個

例2:int a = 2147483648; //0x80 00 00 00, 最高位是1int b = a - 1;printf("a:%d
b:%d
",a,b);結果:a:-2147483648b:2147483647

2.乘除法

乘法就沒什麼討論的,反正問題點就是溢出我們看看除法例3.int a = 4000000000;int b = a/2;printf("a:%d
b:%d
",a,b);a:-294967296b:-147483648

例4.

unsigned int a = 4000000000;int b = a/2;printf("a:%d
b:%d
",a,b);a:-294967296b:2000000000

從上面的例子看到當把 unsigned int 和 int 在運算中的差別,所以我覺得是為了保證十進位數的運算的一致性,2147483648~4294967295 之間的數如果作為 unsigned int 的話,如果用來跟 int 變數進行運算,那麼運算的結果會不可控(主要是 unsigned int 和 int 之間的轉換),除非對編譯器的工作方式很了解;如果指定為 long 的話,那麼和 unsigned int 和 int 進行運算時,結果都是可控的(短類型轉換為長類型沒有信息的丟失)。

為什麼八進位十六進位可以使用 unsigned int,因為八進位和十六進位其實是為了方便表示二進而使用的,八進位的一位表示二進位的三位,十六進位的一位表示二進位的四位,八進位和十六進位不使用負數,因為正負的表示是看最高位是否為1,所以正負就直接在八進位和十六進位的數中表現出來了,不過也極少用八進位和十六進位表示負數,也很少用八進位和十六進位來進行數學運算,一般都是位運算,關心的只是它在內存中的值和長度,不關心它的類型,所以 int 和 unsigned int 也就無所謂。

當然,以上的理解都是基於 long 比 int 要長的情況;如果是一樣長,有沒有 unsigned int 我覺得沒什麼差別。

以上只是我個人的理解···
不懂

10進位數一般都是做數學運算。unsigned類型在數學計算中能不用就不用,所以跳過了所有可以跳過的unsigned,因為有個坑,unsigned是不存在「溢出」的,出錯了都不知道。unsigned數和signed數在cpu計算時是使用的指令是不同的,unsigned上溢下溢不出錯,自動循環。signed根據cpu不同及相關設置不同會置異常位或者產生一個異常。

不過c標準沒有定義有符號型整數溢出的行為,如gcc,專門有關於符號型溢出處理的編譯開關。無符號型是不管的。

-ftrapv This option generates traps for signed overflow on addition, subtraction, multiplication operations. -fwrapv This option instructs the compiler to assume that signed arithmetic overflow of addition, subtraction and multiplication wraps around using twos-complement representation. This flag enables some optimizations and disables other. This option is enabled by default for the Java front-end, as required by the Java language specification.
推薦閱讀:
相关文章