C語(yǔ)言支持多種整型數(shù)據(jù)類型來(lái)表示有限范圍的整數(shù)。每種類型都能用關(guān)鍵字來(lái)指定大小,這些關(guān)鍵字包括char、short、long,同時(shí)還可以指示被表示的數(shù)字是非負(fù)數(shù)(聲明為unsigned)、或者可能是負(fù)數(shù)(默認(rèn))。另外,為這些不同的大小分配的字節(jié)數(shù)根據(jù)程序編譯為32位還是64位而有所不同。
long的大小與int一樣大,屬于歷史遺留問(wèn)題,歷史上的int在16位平臺(tái)時(shí)代是2個(gè)字節(jié),long是4個(gè)字節(jié),所以long要long,等到32位平臺(tái)將int提升到32位一個(gè)字長(zhǎng)時(shí),int和long的長(zhǎng)度就一樣。
不同wordsize對(duì)應(yīng)的最值的16進(jìn)制編碼(其中的U表示Unsigned,T表示Two'scomlement(補(bǔ)碼)):
注意上面的16進(jìn)制與二進(jìn)制的對(duì)應(yīng)關(guān)系:
7:0111
8:1000
F:1111
可以看出有以下數(shù)量關(guān)系:
1無(wú)符號(hào)數(shù)編碼假設(shè)有一個(gè)整數(shù)數(shù)據(jù)類型,有位(wordsizeorwidth)。將其寫(xiě)成向量,將其視為二進(jìn)制表示的無(wú)符號(hào)數(shù),則每一位取值范圍為0或1。
用函數(shù)(BinarytoUnsigned的縮寫(xiě),長(zhǎng)度為)表示為:
函數(shù)將一個(gè)長(zhǎng)度為w的0、1串映射到非負(fù)整數(shù)。
例:
可以用長(zhǎng)度為的指向右側(cè)箭頭的條表示每個(gè)位的位置i。每個(gè)位向量對(duì)應(yīng)的數(shù)值就等于所有值為1的位對(duì)應(yīng)的條的長(zhǎng)度之和。
w位二進(jìn)制所能表示的無(wú)符號(hào)整數(shù)的范圍:
2有符號(hào)整型數(shù)據(jù)的補(bǔ)碼編碼目前,最常見(jiàn)的計(jì)算機(jī)有符號(hào)整型數(shù)的計(jì)算機(jī)表示方式為補(bǔ)碼形式。在補(bǔ)碼中,將字的最高有效位解釋為負(fù)權(quán)(negativeweight)。這里通過(guò)函數(shù)B2T_w(BinarytoTwo's-complement的縮寫(xiě))來(lái)表示:
例:
最高有效位也稱為符號(hào)位,它的“權(quán)重”為,是無(wú)符號(hào)表示中權(quán)重的負(fù)數(shù)。符號(hào)位被設(shè)置為1時(shí),表示值為負(fù),而當(dāng)設(shè)置為0時(shí),值為非負(fù)。
補(bǔ)碼+它的負(fù)數(shù)=模;
補(bǔ)碼和它的負(fù)數(shù)的二進(jìn)制位只有最低位是相同的,其它位都是相補(bǔ):
補(bǔ)碼&&它的負(fù)數(shù)=1;
這也是經(jīng)常有以下表述的來(lái)由:
對(duì)于負(fù)數(shù),補(bǔ)碼等于反碼+1。
補(bǔ)碼使用最高位表示符號(hào),但與無(wú)符號(hào)整數(shù)編碼表示的總體值域的個(gè)數(shù)是一致的:。
我們用向左指的條表示符號(hào)位具有負(fù)權(quán)重。于是,與一個(gè)位向量相關(guān)聯(lián)的數(shù)值是由可能的向左指的條和向右指的條加起來(lái)決定的。
最高位以外的其它位相對(duì)于最高位而言,是一個(gè)此漲彼消的狀態(tài)。
w位二進(jìn)制所能表示的有符號(hào)整數(shù)的范圍:
負(fù)整數(shù)的范圍比正整數(shù)的范圍大1,多出來(lái)的數(shù)就是100…000(二進(jìn)制序列,最小的負(fù)數(shù)),該數(shù)取反加1等于模,截?cái)嗪缶褪?了。
3有符號(hào)整數(shù)與無(wú)符號(hào)整數(shù)之間的轉(zhuǎn)換C語(yǔ)言允許在各種不同的數(shù)字?jǐn)?shù)據(jù)類型之間做強(qiáng)制類型轉(zhuǎn)換。遵循底層的位表示不變,而按不同類型的編碼規(guī)則進(jìn)行解釋的原則。
如果是相關(guān)類型的隱式轉(zhuǎn)換,C語(yǔ)言設(shè)置了一系列的轉(zhuǎn)換規(guī)則。
如果是不相關(guān)類型的強(qiáng)制轉(zhuǎn)換,C語(yǔ)言會(huì)在位級(jí)層面按編碼規(guī)則做重新解釋(解碼)。
例如,假設(shè)變量x聲明為int,u聲明為unsigned,表達(dá)式(unsigned)x會(huì)將x的值轉(zhuǎn)換成一個(gè)無(wú)符號(hào)數(shù)值,而(int)u將u的值轉(zhuǎn)換成一個(gè)有符號(hào)整數(shù)。將有符號(hào)數(shù)強(qiáng)制類型轉(zhuǎn)換成無(wú)符號(hào)數(shù)。另外,一個(gè)表達(dá)式中,如果同時(shí)存在無(wú)符號(hào)整數(shù)與有符號(hào)整數(shù),計(jì)算時(shí),有符號(hào)整數(shù)會(huì)隱式轉(zhuǎn)換為無(wú)符號(hào)整數(shù)。
對(duì)于有符號(hào)整數(shù)的正整數(shù)部分,與無(wú)符號(hào)整數(shù)是一致的。
對(duì)于對(duì)于有符號(hào)整數(shù)的負(fù)整數(shù)部分,與無(wú)符號(hào)整數(shù)只有最高位的解釋不同,其它位是一致的。
所以有以下數(shù)量關(guān)系:
負(fù)整數(shù)x→U=x+
u→負(fù)整數(shù)x=U-
由于C語(yǔ)言對(duì)同時(shí)包含有符號(hào)和無(wú)符號(hào)整型數(shù)表達(dá)式的隱式轉(zhuǎn)換,會(huì)出現(xiàn)一些奇特的行為。當(dāng)執(zhí)行一個(gè)運(yùn)算時(shí),如果它的一個(gè)運(yùn)算數(shù)是有符號(hào)的而另一個(gè)是無(wú)符號(hào)的,那么C語(yǔ)言會(huì)隱式地將有符號(hào)整數(shù)強(qiáng)制類型轉(zhuǎn)換為無(wú)符號(hào)數(shù),并假設(shè)這兩個(gè)數(shù)都是非負(fù)的,來(lái)執(zhí)行這個(gè)運(yùn)算。就像我們將要看到的,這種***對(duì)于標(biāo)準(zhǔn)的算術(shù)運(yùn)算來(lái)說(shuō)并無(wú)多大差異,但是對(duì)于像<和>這樣的關(guān)系運(yùn)算符來(lái)說(shuō),它會(huì)導(dǎo)致非直觀的結(jié)果。
有符號(hào)數(shù)到無(wú)符號(hào)數(shù)的隱式強(qiáng)制類型轉(zhuǎn)換導(dǎo)致了某些非直觀的行為。而這些非直觀的特性經(jīng)常導(dǎo)致程序錯(cuò)誤,并且這種包含隱式強(qiáng)制類型轉(zhuǎn)換的細(xì)微差別的錯(cuò)誤很難被發(fā)現(xiàn)。
4擴(kuò)展一個(gè)整型數(shù)字的位表示一個(gè)常見(jiàn)的運(yùn)算是在不同字長(zhǎng)的整數(shù)之間轉(zhuǎn)換,同時(shí)又保持?jǐn)?shù)值不變。
對(duì)于有符號(hào)整數(shù)和無(wú)符號(hào)整數(shù),兩者在擴(kuò)展時(shí),高位的符號(hào)擴(kuò)展有所區(qū)別:
shortsx=val;/*-12345*/unsignedshortusx=sx;/*53191*/intx=sx;/*-12345*/unsignedux=usx;/*53191*/在采用補(bǔ)碼表示的32位大端法機(jī)器上的內(nèi)存布局:
有符號(hào)整型使用1來(lái)做符號(hào)擴(kuò)展。
無(wú)符號(hào)整型使用0來(lái)做符號(hào)擴(kuò)展。
有符號(hào)整數(shù)在做右移運(yùn)算時(shí),也會(huì)存在符號(hào)擴(kuò)展的問(wèn)題。
5截?cái)鄶?shù)字假設(shè)我們不用額外的位來(lái)擴(kuò)展一個(gè)數(shù)值,而是減少表示一個(gè)數(shù)字的位數(shù)。例如下面代碼中這種情況:
intx=53191;shortsx=(short)x;/*-12345*/inty=sx;/*-12345*/當(dāng)我們把x強(qiáng)制類型轉(zhuǎn)換為short時(shí),我們就將32位的int截?cái)酁榱?6位的shortint。
當(dāng)將一個(gè)w位的整型數(shù)截?cái)酁橐粋€(gè)k位整型數(shù)字時(shí),我們會(huì)丟棄高w-k位。
無(wú)符號(hào)整數(shù)取模:
有符號(hào)整數(shù)的補(bǔ)碼取模后要做轉(zhuǎn)換:
6整數(shù)運(yùn)算與溢出C語(yǔ)言的整型數(shù)字是一個(gè)有限字長(zhǎng)的表示,當(dāng)在計(jì)算時(shí)存在溢出時(shí),會(huì)做模運(yùn)算處理。
當(dāng)兩個(gè)w位的無(wú)符號(hào)整型相加時(shí),其結(jié)果可能是一個(gè)w+1位的無(wú)符號(hào)數(shù)。
當(dāng)兩個(gè)w位的無(wú)符號(hào)整型相加時(shí)其和等于或超過(guò)時(shí),就會(huì)發(fā)生溢出,其結(jié)果為和與的模運(yùn)算:
補(bǔ)碼加法存在正溢出與負(fù)溢出:
首先要明白,一個(gè)正數(shù)和一個(gè)負(fù)數(shù)相加,結(jié)果一定不會(huì)溢出(因?yàn)榻Y(jié)果的絕對(duì)值一定小于兩個(gè)加數(shù)的絕對(duì)值,兩個(gè)加數(shù)都表達(dá)出來(lái)了,結(jié)果一定能表達(dá)出來(lái)。)
所以,發(fā)生溢出的情況一定是符號(hào)相同的兩個(gè)數(shù)相加。
分情況討論:
①正整數(shù)pa+正整數(shù)pb=r
符號(hào)位0,數(shù)位相加,如果結(jié)果的符號(hào)位變成1了,那一定是兩個(gè)加數(shù)的最高位相加進(jìn)上來(lái)的。
假設(shè)將一個(gè)長(zhǎng)度w=4的0、1串映射到有符號(hào)整數(shù),其最大的正數(shù)只能是0111,也就是7。
0111+0001=1000//7+1=8-2^4=-8,向下溢出
0111+0011=1010//7+3=10-2^4=-6,向下溢出
發(fā)生向下溢出,判斷的方式之一是:r<pa||r<pb。
②負(fù)整數(shù)na+負(fù)整數(shù)nb=r
符號(hào)位都是1,所以符號(hào)位一定會(huì)進(jìn)位。數(shù)位相加,如果最后符號(hào)位是0,說(shuō)明結(jié)果變成正的了,那一定是發(fā)生溢出了(負(fù)+負(fù)!=正)。
假設(shè)將一個(gè)長(zhǎng)度w=4的0、1串映射到有符號(hào)整數(shù),其最小的負(fù)數(shù)只能是1000,也就是-8。
1000+1111=11000->0110//-8+(-1)=-9+2^4=7,向上溢出
1011+1011=10110->0110//-5+(-5)=-10+2^4=6,向上溢出
另外一種情況,沒(méi)有改變符號(hào)位的溢出,屬正常溢出,正如有符號(hào)位擴(kuò)展、算術(shù)移位一樣:
1100+1100=10000->0000//-4+(-4)=-8,正常溢出
1111+1111=11110->1110//-1+(-1)=-2,正常溢出
發(fā)生向上溢出,判斷的方式之一是:r<pa||r<pb。
CPU的標(biāo)志寄存器有一個(gè)溢出標(biāo)志位,反映有符號(hào)數(shù)加減運(yùn)算是否溢出。如果運(yùn)算結(jié)果超過(guò)了8位或者16位有符號(hào)數(shù)的表示范圍,則OF置1,否則置0。
7整數(shù)的乘除①兩整數(shù)乘除的結(jié)果還是一個(gè)整數(shù)類型(類型一致),如果是除法,可能存在舍入(舍入到零,向上舍入,向下舍入)的情況;
②兩整數(shù)乘法要考慮溢出的情況。
③位運(yùn)算也是一種特殊的整數(shù)乘除,乘數(shù)與除數(shù)是2的不同的整數(shù)冪(當(dāng)一個(gè)整數(shù)乘除一個(gè)常數(shù)時(shí),這個(gè)常數(shù)如果不是某個(gè)整數(shù)次冪,可以拆解成不同的冪次的加減)。
8代碼示例8.1帶符號(hào)數(shù)產(chǎn)生意外結(jié)果的例子。這個(gè)例子會(huì)造成無(wú)限循環(huán),因?yàn)閟izeof會(huì)返回unsignedint類型,由此帶來(lái)的結(jié)果是,i-sizeof(char)這個(gè)表達(dá)式的求值結(jié)果將會(huì)是unsignedint(隱式轉(zhuǎn)換!!),i會(huì)隱式轉(zhuǎn)換為unsigned,i從0減1后變成-1,其二進(jìn)制編碼是0xFFFFFFFF,轉(zhuǎn)換成無(wú)符號(hào)數(shù)是2^32-1,從而產(chǎn)生無(wú)限循環(huán),有時(shí)候你需要特別留心這種不經(jīng)意的錯(cuò)誤!
intn=10,i;for(i=n-1;i-sizeof(char)>=0;i--)printf("i:0x%x ",i);if(-1>0U)//-1的二進(jìn)制編碼是0xFFFFFFFF,轉(zhuǎn)變成無(wú)符號(hào)數(shù)是2^32-1printf("YouSurprisedme! ");8.2以下是2002年的freeBSD內(nèi)核的部分代碼,其中包含了漏洞,假設(shè)惡意人員將負(fù)值作為maxlen傳入這個(gè)函數(shù),有發(fā)生什么情況?
以下size_t的類型是typedefunsignedintsize_t的類型定義:
#defineKSIZE1024charkbuf[KSIZE];/*Copyatmostmaxlenbytesfromkernelregiontouserbuffer*/intcopy_from_kernel(void*user_dest,intmaxlen){intlen=KSIZE<maxlen?KSIZE:maxlen;memcpy(user_dest,kbuf,len);returnlen;}/*Declarationoflibraryfunctionmemcpy*/void*memcpy(void*dest,void*src,size_tn);/*MaliciousUsage*/voidgetstuff(){charmybuf[MSIZE];copy_from_kernel(mybuf,-512);//-512轉(zhuǎn)變成無(wú)符號(hào)數(shù)后是2^31+512}8.3給定一個(gè)有序的整型數(shù)組,請(qǐng)編程實(shí)現(xiàn)二分查找算法。
高德納在《計(jì)算機(jī)程序設(shè)計(jì)的藝術(shù)》指出,雖然早在1946年就有人將二分查找的***公諸于世,但直到1962年才有人寫(xiě)出沒(méi)有bug的二分查找程序,可見(jiàn),寫(xiě)一個(gè)安全的代碼并不容易,你是不是一不小心就寫(xiě)出像下面這樣的二分查找代碼了?
intbinary_search(inta[],intlen,intkey){intlow=0;inthigh=len-1;while(low<=high){intmid=(low+high)/2;//提示:這里有溢出Bug!if(a[mid]==key){returnmid;}if(key<a[mid]){high=mid-1;}else{low=mid+1;}}return-1;}ref
RandalE.Bryant,DavidR.O’Hallaron《ComputerSystems:AProgrammersPerspective》
-End-