Q1:int類(lèi)型在內(nèi)存中是以何種方式存儲(chǔ)的?
要解決這個(gè)問(wèn)題,我們需要首先比較深入地理解下int類(lèi)型。
?
本文中的int類(lèi)型的相關(guān)數(shù)據(jù)都以32位操作系統(tǒng)下的VC++6.0編譯器環(huán)境為準(zhǔn)。在下表中可以看到,int類(lèi)型表示帶有符號(hào)的整型,而unsigned int類(lèi)型為無(wú)符號(hào)的整型。
類(lèi)型名稱(chēng) | 占字節(jié)數(shù) | 取值范圍 |
int | 4B | -2^31~2^31-1 |
unsigned int | 4B | 0 ~ 2^32 |
1、占用的比特位數(shù)量
在32位操作系統(tǒng)下,兩者都是占用4個(gè)字節(jié),每個(gè)字節(jié)有8個(gè)比特位,因此有32個(gè)0-1的二進(jìn)制位數(shù)。兩者的不同在于,int類(lèi)型有正負(fù)號(hào)(±)的存在,需要比unsigned int類(lèi)型多消耗一個(gè)位數(shù)。
2、符號(hào)的表示方法
????? 在所有被int類(lèi)型占用的比特位中,左起第一個(gè)位就是符號(hào)位。int類(lèi)型的符號(hào)位上,0表示正數(shù),1表示負(fù)數(shù)。在32位操作系統(tǒng)下,其余后面31位是數(shù)值位。
3、數(shù)字0的表示方法
????? 按照上面提到的符號(hào),我們有了兩種0的表示方法,即“+0”和“-0”。
????? 實(shí)際上,在32位系統(tǒng)下int類(lèi)型中,我們計(jì)算機(jī)已經(jīng)強(qiáng)行規(guī)定了這種情況,數(shù)字0采用“+0”的表示方法,即00000000 00000000 00000000;而“-0”這個(gè)特殊的數(shù)字被定義為了-2^31。
????? 因此我們看到32位系統(tǒng)下int類(lèi)型的取值范圍中,負(fù)數(shù)部分比正數(shù)部分多了一個(gè)數(shù)字,正數(shù)的最大取值是2^31-1,而負(fù)數(shù)的最小取值是-2^31。正數(shù)部分之所以要減去1,是因?yàn)楸粩?shù)字0占用了,而負(fù)數(shù)部分不需要用來(lái)表示0,因此原本的“-0”就用來(lái)表示-2^31這個(gè)數(shù)字。
?
那么是不是實(shí)現(xiàn)了上面已經(jīng)提到的int類(lèi)型的深入理解,我們就可以知道內(nèi)存中int類(lèi)型的數(shù)據(jù)表達(dá)了呢?
比如int類(lèi)型的數(shù)字“-1”,按照上面的理解方式,在內(nèi)存中32個(gè)比特位上應(yīng)該是這樣子的:10000000 00000000 00000001,左邊第一個(gè)1表示負(fù)號(hào),后面31位表示數(shù)值部分“1”。實(shí)際情況并不是這樣。這里就需要引入“補(bǔ)碼”這個(gè)概念了。
Q2:什么是“補(bǔ)碼”?
???? 要回到這個(gè)問(wèn)題,得額外補(bǔ)充兩個(gè)概念,“原碼”和“反碼”。
?
????? 計(jì)算機(jī)中的符號(hào)數(shù)有三種表示方法,即原碼、反碼和補(bǔ)碼。三種表示方法均有符號(hào)位和數(shù)值位兩部分,符號(hào)位都是用0表示“正”,用1表示“負(fù)”,而數(shù)值位,三種表示方法各不相同。
1、原碼(true form)
????? 原碼,是計(jì)算機(jī)中一種對(duì)數(shù)字的二進(jìn)制定點(diǎn)表示方法。原碼表示法在數(shù)值前面前面有一位符號(hào)位(即最高位為符號(hào)位),正數(shù)該位為0,負(fù)數(shù)該位為1(0有兩種表示:+0和-0),其余位表示數(shù)值的大小。
????? 怎么樣,是不是覺(jué)得眼熟,沒(méi)錯(cuò)!Q1中結(jié)尾提到的int類(lèi)型數(shù)值“-1”的32位二進(jìn)制就是原碼,即10000000 00000000 00000001。與之對(duì)應(yīng)的,正數(shù)“+1”就是00000000 00000000 00000001。
????? 那么為何不用原碼在內(nèi)存中表示數(shù)值呢?
我們舉個(gè)例子(以8位二進(jìn)制表示)
十進(jìn)制 | 原碼 |
1 | 0000 0001 |
-1 | 1000 0001 |
結(jié)果(原碼) | 1000 0010 |
結(jié)果(十進(jìn)制) | -2 |
上述結(jié)果換算成十進(jìn)制為-2,這顯然出錯(cuò)了。這是由于計(jì)算機(jī)在計(jì)算時(shí)以加法進(jìn)行計(jì)算的算法更簡(jiǎn)便,減法先轉(zhuǎn)換為負(fù)數(shù),再進(jìn)行加法運(yùn)算。因此,原碼的符號(hào)位不能直接參與運(yùn)算。
總結(jié):原碼是有符號(hào)數(shù)的最簡(jiǎn)單的編碼方式,便于輸入輸出,但作為代碼加減運(yùn)算時(shí)較為復(fù)雜,故計(jì)算機(jī)一般不采用這種編碼方式存儲(chǔ)符號(hào)數(shù)。
2、反碼(ones’ complement)
??? 首先我們來(lái)了解下反碼表示法的規(guī)定:“正數(shù)的反碼與其原碼相同;負(fù)數(shù)的反碼是對(duì)其原碼逐位取反,但符號(hào)位除外?!?/span>
????? 什么意思呢?舉個(gè)例子說(shuō)明下:
- 對(duì)于正數(shù)和“+0”而言,其原碼本身就是反碼,例如 8位二進(jìn)制“+1”,其原碼與反碼都是00000001;
- 對(duì)于負(fù)數(shù)和“-0”而言,符號(hào)位與原碼中一樣,保持不變,其余位數(shù)逐位取反,1換成0,0換成1,例如 “-1”,其8位二進(jìn)制原碼是1000 0001,其反碼是1111 1110;
那么我們是否已經(jīng)可以正常進(jìn)行運(yùn)算了呢?
我們舉個(gè)三個(gè)例子:
例一:1+2=3(以8位二進(jìn)制表示)
十進(jìn)制 | 原碼 | 反碼 |
1 | 0000 0001 | 0000 0001 |
2 | 0000 0010 | 0000 0010 |
結(jié)果(反碼) | 0000 0011 | |
結(jié)果(原碼) | 0000 0011 | |
結(jié)果(十進(jìn)制) | 3 |
計(jì)算結(jié)果正確。
例二:1+(-2)=-1
十進(jìn)制 | 原碼 | 反碼 |
1 | 0000 0001 | 0000 0001 |
-2 | 1000 0010 | 1111 1101 |
結(jié)果(反碼) | 1111 1110 | |
結(jié)果(原碼) | 1000 0001 | |
結(jié)果(十進(jìn)制) | -1 |
????? 計(jì)算結(jié)果正確。
例三:1+(-1)=0
十進(jìn)制 | 原碼 | 反碼 |
1 | 0000 0001 | 0000 0001 |
-1 | 1000 0001 | 1111 1110 |
結(jié)果(反碼) | 1111 1111 | |
結(jié)果(原碼) | 1000 0000 | |
結(jié)果(十進(jìn)制) | -0 |
????? 計(jì)算結(jié)果為-0,問(wèn)題來(lái)了,由于-0的存在,使得二進(jìn)制與十進(jìn)制的互換不再是一一對(duì)應(yīng)的關(guān)系。
????? 總結(jié):由于-0這個(gè)問(wèn)題的存在,會(huì)使得計(jì)算機(jī)需要增加額外的物理硬件配合運(yùn)算,所以在計(jì)算機(jī)發(fā)展的早期就已經(jīng)拋棄了使用反碼儲(chǔ)存數(shù)據(jù)。
3、補(bǔ)碼
????? 補(bǔ)碼正是基于反碼的“-0”問(wèn)題誕生的,可以解決這個(gè)問(wèn)題。
??? 補(bǔ)碼的計(jì)算方法是:正數(shù)和+0的補(bǔ)碼是其本身,負(fù)數(shù)則先計(jì)算其反碼,然后反碼加上1,得到補(bǔ)碼。
????? 補(bǔ)碼換算為原碼的過(guò)程中,如果補(bǔ)碼是正數(shù)或者+0的補(bǔ)碼,則其原碼就是補(bǔ)碼本身;如果補(bǔ)碼是負(fù)數(shù)或者-0的補(bǔ)碼,則其原碼的計(jì)算方法是,先將補(bǔ)碼減掉1,得到反碼,再將反碼取反,得到原碼。
????? 以上的說(shuō)法有些繞,但是補(bǔ)碼的算法應(yīng)該已經(jīng)說(shuō)清楚了。下面舉一些例子。
例一:1+(-1)=0
十進(jìn)制 | 原碼 | 反碼 | 補(bǔ)碼 |
1 | 0000 0001 | 0000 0001 | 0000 0001 |
-1 | 1000 0001 | 1111 1110 | 1111 1111 |
結(jié)果(補(bǔ)碼) | 0000 0000 | ||
結(jié)果(反碼) | 0000 0000 | ||
結(jié)果(原碼) | 0000 0000 | ||
結(jié)果(十進(jìn)制) | +0 |
計(jì)算結(jié)果正確,+0即是數(shù)字0的唯一表示。
例二:1+2=3
十進(jìn)制 | 原碼 | 反碼 | 補(bǔ)碼 |
1 | 0000 0001 | 0000 0001 | 0000 0001 |
2 | 0000 0010 | 0000 0010 | 0000 0010 |
結(jié)果(補(bǔ)碼) | 0000 0011 | ||
結(jié)果(反碼) | 0000 0011 | ||
結(jié)果(原碼) | 0000 0011 | ||
結(jié)果(十進(jìn)制) | 3 |
計(jì)算結(jié)果正確。
例三:1+(-2)=-1
十進(jìn)制 | 原碼 | 反碼 | 補(bǔ)碼 |
1 | 0000 0001 | 0000 0001 | 0000 0001 |
-2 | 1000 0010 | 1111 1101 | 1111 1110 |
結(jié)果(補(bǔ)碼) | 1111 1111 | ||
結(jié)果(反碼) | 1111 1110 | ||
結(jié)果(原碼) | 1000 0001 | ||
結(jié)果(十進(jìn)制) | -1 |
計(jì)算結(jié)果正確。
特別地,我們加入例四:(-1)+(-127)=-128
我們知道8位二進(jìn)制的符號(hào)數(shù)的取值范圍是(-2^7)~(2^7-1),即-128~127。
十進(jìn)制 | 原碼 | 反碼 | 補(bǔ)碼 |
-1 | 1000 0001 | 1111 1110 | 1111 1111 |
-127 | 1111 1111 | 1000 0000 | 1000 0001 |
結(jié)果(補(bǔ)碼) | 1000 0000 | ||
結(jié)果(反碼) | |||
結(jié)果(原碼) | |||
結(jié)果(十進(jìn)制) | -128 |
由于補(bǔ)碼1000 0000具有特殊性,計(jì)算機(jī)在編寫(xiě)底層算法時(shí),將其規(guī)定為該取值范圍中的最小數(shù)-128,其值與(-1)+(-127)的計(jì)算結(jié)果正好符合。
補(bǔ)充一點(diǎn),8位二進(jìn)制補(bǔ)碼1000 0000沒(méi)有對(duì)應(yīng)的反碼和原碼,其他位數(shù)的二進(jìn)制補(bǔ)碼與此類(lèi)似。
?
通過(guò)以上兩個(gè)問(wèn)題Q1和Q2的回答和引申,我們已經(jīng)知道int類(lèi)型在內(nèi)存中存儲(chǔ)的方式,即int類(lèi)型在內(nèi)存中,以補(bǔ)碼的形式存儲(chǔ)。而且我們還知道了為何int類(lèi)型的取值范圍中負(fù)數(shù)的最小值的絕對(duì)值比正數(shù)的最大值大1的原因,即-2^32的補(bǔ)碼是10000000 00000000 00000000,原本-0的位置被-2^32取代了。
怎么樣,明白了嗎?
? ? ?作者:dotcpp-菜鳥(niǎo)
C語(yǔ)言研究中心(www.sztianhecheng.cn)