有符號類型數據帶來的原碼、反碼、補碼思考

前段時間發現一個比較有趣的編程問題:
下面的程序輸出什麼?

void func(){
	unsigned int a = 5u;
	int b = -10;
	if( (a + b) < 0 ){
		printf("5 - 10 < 0\n");
	}
	else
		printf("5 - 10 >0\n");
}

int main(int argc,char** argv){
	func();
	return EXIT_SUCCESS;
}

結果

相信上當者十之八九認爲: 5 - 10 == -5;-5 < 0成立;程序輸出:5 - 10 < 0。然後就讓出題者貽笑大方了。

估計是個程序員都知道一個常識,那就是馮諾依曼計算機推翻了傳統的十進制計數方式,採用二進制計數,這一體系結構的確定,突破了當時開發模擬式計算機的技術瓶頸,數字式計算機自此飛速發展。(貌似說了一句廢話…)

==============================================
很顯然,這個題目考察了:
1.編譯器對不同數據類型做運算時的默認類型轉換處理;
2.有符號類型數據在內存中的補碼存儲。

==============================================
1.C/C++編譯器默認的類型轉換:
計算機最初設計,就是爲了做科學計算,針對這種設計初衷,當面對不同數據類型做運算的時候,計算機的設計者肯定希望的是計算機能夠完成這一運算。儘管早期的計算機內存資源並不豐富,但是爲了完成不同類型之間的數據的運算,計算機設計者還是忍痛地做了這麼一個決定:當兩個數據類型不同的對象進行運算的時候,其運算結果總是以能夠存儲更大數據值的形式存儲。比如說:int 和 char 做 + 運算,那麼他們的結果就以int的形式存儲,因爲int能夠存儲的容量更大;而unsigned int和int做運算,結果則是以ungined int的形式存儲,因爲unsigned int能夠存儲的容量更大。

==============================================
2.有符號類型數據在內存中的補碼存儲。
C語言中,原碼、反碼、補碼都是有符號定點數的表示方法。
一個有符號定點數的最高位爲符號位,0代表正,1代表負。
以下以8位整數爲例:
原碼就是這個數本身的二進制形式。
例如
1000 0001 就是-1
0000 0001 就是+1

正數的反碼、補碼和原碼都相同。

對於負數而言
反碼 = 原碼的符號位不變,其餘各位取反。
補碼 = 反碼+1
舉例8位-1而言:
其原碼 = 1000 0001
其反碼 = 1111 1110
其補碼 = 1111 1111

這裏引出一個困擾我許久的問題:一段8位的內存空間存儲1000 0001,那麼,這個數據到底是有符號的數據-1呢?還是無符號數據129呢?到底怎麼識別?很不幸的,這是一個無解的問題,就如你想的那樣,沒有辦法直接識別出一個最高位爲1二進制數到底是有符號數還是無符號數。至於編譯器爲什麼能夠識別出,我猜想的是編譯器內部在對變量進行內存開闢的時候做了標識符標定吧(此處如果有高見還望多多指教)。

==============================================
鋪墊結束,回到題目當中,在func函數中,定義了unsigned int a;和int b;
當a + b運算出現時,站在編譯器的角度來看,需要把b轉換爲無符號類型的數據,然後再與a做加法運算,最終結果與0比較。編譯器是這麼想的,它也這麼做了。假設在32位系統中,這樣做導致b(-10)(1<27個0>1010)被識別爲2147483658u,然後 a(5u) + 2147483658u == 2147483663u;由此,2147483663u < 0顯然是不成立的;所以,程序最終輸出是表面上看上去的十分不可思議的:5 - 10 > 0;然而它確確實實發生了。

==============================================
帶來一點關於數據移位的擴展思考
在進行軟件開發的時候,我們經常會對數據進行移位運算,思考一個問題:移位運算的時候,數據缺失掉的位一直都是補0嗎?
不是這樣的,進行移位運算,對缺失數據位的填補,有時候也補1。那麼移位運算補1和補0什麼時候發生?總結如下:

對於無符號數,無論數據是左移(<<)還是右移(>>),數據缺失的位永遠都是填補0。
例如

unsigned char uCharData = 0xFF;
//左移1位
uCharData  <<= 1;
printf("when << 1 bit, uCharData = %x\n",uCharData); //十六進制打印

//重置數據並右移1位
uCharData = 0xFF;
uCharData >>= 1;
printf("when << 1 bit, uCharData = %x\n",uCharData); //十六進制打印

輸出結果:
無符號移位輸出
顯然,無論左移還是右移,往缺失位上填充的數據都是0。

對於有符號數據而言,當發生左移(<<)時,缺失的數據爲填補0,發生右移時,缺失的數據爲填補1。

	int intData = -1;
	
	//左移1位
	intData  <<= 1;
	printf("when << 1 bit, intData = %x\n",intData); //十六進制打印

	//重置數據並右移1位
	intData = -1;
	intData >>= 1;
	printf("when << 1 bit, intData = %x\n",intData); //十六進制打印

有符號整形
值得注意的是,這次使用了int類型的數據,它是有符號類型數據,程序運行在64位機器上。

-1在存儲在內存中的補碼爲0xffffffff,左移1位,變成0xfffffffe,說明左移時候對缺失位補0處理;當右移1位之後,內存中存儲的數據還是0xffffffff,說明,右移之後,缺失的位填補的是1。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章