Q1:int類型在內存中是以何種方式存儲的?
要解決這個問題,我們需要首先比較深入地理解下int類型。
{
本文中的int類型的相關數據,如無特別說明都以32位操作系統下的VC++6.0編譯器環境為准。
在下表中可以看到,int類型表示帶有符號的整型,而unsigned int類型為無符號的整型。
類型名稱 |
占字節數 |
取值范圍 |
int |
4B |
-2^31~2^31-1 |
unsigned int |
4B |
0 ~ 2^32 |
1、占用的比特位數量
在32位操作系統下,兩者都是占用4個字節,每個字節有8個比特位,因此有32個0-1的二進制位數。兩者的不同在於,int類型有正負號(±)的存在,需要比unsigned int類型多消耗一個位數。
2、符號的表示方法
在所有被int類型占用的比特位中,左起第一個位(即最高位)就是符號位。int類型的符號位上,0表示正數,1表示負數。在32位操作系統下,其余后面31位是數值位。
3、數字0的表示方法
按照上面提到的符號,我們有了兩種0的表示方法,即“+0”和“-0”。
實際上,在32位系統下int類型中,我們計算機已經強行規定了這種情況,數字0采用“+0”的表示方法,即0000000000000000 00000000;而“-0”這個特殊的數字被定義為了-2^31。
因此我們看到32位系統下int類型的取值范圍中,負數部分比正數部分多了一個數字,正數的最大取值是2^31-1,而負數的最小取值是-2^31。正數部分之所以要減去1,是因為被數字0占用了“+0”,而負數部分不需要用來表示0,因此原本的“-0”就用來表示-2^31這個數字。
}
那么是不是實現了上面已經提到的int類型的深入理解,我們就可以知道內存中int類型的數據表達了呢?
比如int類型的數字“-1”,按照上面的理解方式,在內存中32個比特位上應該是這樣子的:10000000 00000000 00000001,左邊第一個1表示負號,后面31位表示數值部分“1”。實際情況並不是這樣。這里就需要引入“補碼”這個概念了。
Q2:什么是“補碼”?
要回到這個問題,得額外補充兩個概念,“原碼”和“反碼”。
{
計算機中的符號數有三種表示方法,即原碼、反碼和補碼。三種表示方法均有符號位和數值位兩部分,符號位都是用0表示“正”,用1表示“負”,而數值位,三種表示方法各不相同。
1、原碼(true form)
原碼,是計算機中一種對數字的二進制定點表示方法。原碼表示法在數值前面前面有一位符號位(即最高位為符號位),正數該位為0,負數該位為1(0有兩種表示:+0和-0),其余位表示數值的大小。
怎么樣,是不是覺得眼熟,沒錯!Q1中結尾提到的int類型數值“-1”的32位二進制就是原碼,即10000000 00000000 00000001。與之對應的,正數“+1”就是00000000 00000000 00000001。
那么為何不用原碼在內存中表示數值呢?
我們舉個例子(以8位二進制表示)
十進制 |
原碼 |
1 |
0000 0001 |
-1 |
1000 0001 |
結果(原碼) |
1000 0010 |
結果(十進制) |
-2 |
上述結果換算成十進制為-2,這顯然出錯了。這是由於計算機在計算時以加法進行計算的算法更簡便,減法先轉換為負數,再進行加法運算。因此,原碼的符號位不能直接參與運算。
總結:原碼是有符號數的最簡單的編碼方式,便於輸入輸出,但作為代碼加減運算時較為復雜,故計算機一般不采用這種編碼方式存儲符號數。
2、反碼(ones' complement)
首先我們來了解下反碼表示法的規定:“正數的反碼與其原碼相同;負數的反碼是對其原碼逐位取反,但符號位除外。”
什么意思呢?舉個例子說明下:
① 對於正數和“+0”而言,其原碼本身就是反碼,例如 8位二進制“+1”,其原碼與反碼都是00000001;
② 對於負數和“-0”而言,符號位與原碼中一樣,保持不變,其余位數逐位取反,1換成0,0換成1,例如 “-1”,其8位二進制原碼是10000001,其反碼是1111 1110;
那么我們是否已經可以正常進行運算了呢?
我們舉個三個例子:
例一:1+2=3(以8位二進制表示)
十進制 |
原碼 |
反碼 |
1 |
0000 0001 |
0000 0001 |
2 |
0000 0010 |
0000 0010 |
結果(反碼) |
|
0000 0011 |
結果(原碼) |
|
0000 0011 |
結果(十進制) |
|
3 |
計算結果正確。
例二:1+(-2)=-1
十進制 |
原碼 |
反碼 |
1 |
0000 0001 |
0000 0001 |
-2 |
1000 0010 |
1111 1101 |
結果(反碼) |
|
1111 1110 |
結果(原碼) |
|
1000 0001 |
結果(十進制) |
|
-1 |
計算結果正確。
例三:1+(-1)=0
十進制 |
原碼 |
反碼 |
1 |
0000 0001 |
0000 0001 |
-1 |
1000 0001 |
1111 1110 |
結果(反碼) |
|
1111 1111 |
結果(原碼) |
|
1000 0000 |
結果(十進制) |
|
-0 |
計算結果為-0,問題來了,由於-0的存在,使得二進制與十進制的互換不再是一一對應的關系。
總結:由於-0這個問題的存在,會使得計算機需要增加額外的物理硬件配合運算,所以在計算機發展的早期就已經拋棄了使用反碼儲存數據。
3、補碼
補碼正是基於反碼的“-0”問題誕生的,可以解決這個問題。
補碼的計算方法是:正數和+0的補碼是其原碼,負數則先計算其反碼,然后反碼加上1,得到補碼。
補碼換算為原碼的過程中,如果補碼是正數或者+0的補碼,則其原碼就是補碼本身;如果補碼是負數或者-0的補碼,則其原碼的計算方法是,先將補碼減掉1,得到反碼,再將反碼取反,得到原碼。
以上的說法有些繞,但是補碼的算法應該已經說清楚了。下面舉一些例子。
例一:1+(-1)=0
十進制 |
原碼 |
反碼 |
補碼 |
1 |
0000 0001 |
0000 0001 |
0000 0001 |
-1 |
1000 0001 |
1111 1110 |
1111 1111 |
結果(補碼) |
|
|
0000 0000 |
結果(反碼) |
|
|
0000 0000 |
結果(原碼) |
|
|
0000 0000 |
結果(十進制) |
|
|
+0 |
計算結果正確,+0即是數字0的唯一表示。
例二:1+2=3
十進制 |
原碼 |
反碼 |
補碼 |
1 |
0000 0001 |
0000 0001 |
0000 0001 |
2 |
0000 0010 |
0000 0010 |
0000 0010 |
結果(補碼) |
|
|
0000 0011 |
結果(反碼) |
|
|
0000 0011 |
結果(原碼) |
|
|
0000 0011 |
結果(十進制) |
|
|
3 |
計算結果正確。
例三:1+(-2)=-1
十進制 |
原碼 |
反碼 |
補碼 |
1 |
0000 0001 |
0000 0001 |
0000 0001 |
-2 |
1000 0010 |
1111 1101 |
1111 1110 |
結果(補碼) |
|
|
1111 1111 |
結果(反碼) |
|
|
1111 1110 |
結果(原碼) |
|
|
1000 0001 |
結果(十進制) |
|
|
-1 |
計算結果正確。
特別地,我們加入例四:(-1)+(-127)=-128
我們知道8位二進制的符號數的取值范圍是(-2^7)~(2^7-1),即-128~127。
十進制 |
原碼 |
反碼 |
補碼 |
-1 |
1000 0001 |
1111 1110 |
1111 1111 |
-127 |
1111 1111 |
1000 0000 |
1000 0001 |
結果(補碼) |
|
|
1000 0000 |
結果(反碼) |
|
|
|
結果(原碼) |
|
|
|
結果(十進制) |
|
|
-128 |
由於補碼10000000具有特殊性,計算機在編寫底層算法時,將其規定為該取值范圍中的最小數-128,其值與(-1)+(-127)的計算結果正好符合。
補充一點,8位二進制補碼1000 0000沒有對應的反碼和原碼,其他位數的二進制補碼與此類似。
}
通過以上兩個問題Q1和Q2的回答和引申,我們已經知道int類型在內存中存儲的方式,即int類型在內存中,以補碼的形式存儲。而且我們還知道了為何int類型的取值范圍中負數的最小值的絕對值比正數的最大值大1的原因,即-2^31的補碼是1000000000000000 00000000,原本-0的位置被-2^31取代了。