1. 程式人生 > >有符號數與無符號數的強制型別轉換問題

有符號數與無符號數的強制型別轉換問題

在C語言中有符號數轉化為無符號會出現一些問題,先看以下的程式例子:

int main()
{
	char ch[12] = {0xF0, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00};
	unsigned int result = (ch[7] << 8) + ch[6];     // ch[7]為0x0F, ch[6]為0xFF
	printf("result(0x%x), ch[7](0x%x), ch[6](0x%x)\n", result, ch[7], ch[6]);
	return 0;
}
原本我以為計算結果為 result=0xFFF,但實際的計算結果是result=0xEFF。以下是程式的輸出:

原來ch[6]在char轉成unsigned int時由0xff轉為了0xffffffff。它的符號位(最高位)為1,在轉換成無符號數時將其他位(第9位至第32位)全置為1。所以最後的計算result的結果不0xFFF而是0xEFF。我們來看一下反彙編,就更加清楚了。

	unsigned int result = (ch[7] << 8) + ch[6];
00411C68  movsx       eax,byte ptr [ebp-0Dh] 
00411C6C  shl         eax,8 
00411C6F  movsx       ecx,byte ptr [ebp-0Eh] 
00411C73  add         eax,ecx 
00411C75  mov         dword ptr [ebp-20h],eax 
從上面可以看到使用了movsx指令來進行資料的傳輸。movsx是帶符號擴充套件傳送指令,帶符號擴充套件的意思就是將擴充套件的那些位都用符號位的值來補全。如8位的資料0xff,轉換成32位的資料就是0xffffffff(因為它的符號位為1)。如8位的資料0x3a,轉換後的值就是0x3a(因為它的符號們為0)。從反彙編後我們看到轉換的過程:
1. 將8位的有符號數擴充套件成32位的有符號數
2. 對擴充套件後的32位有符號數進行移位和相加操作
3. 將有符號數以無符號數的形式顯示

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

我們看一下不同位元組數的無符號數轉換例子:

int main()
{
	unsigned char ch[12] = {0xF0, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00};
	unsigned int result = (ch[7] << 8) + ch[6];
	printf("result(0x%x), ch[7](0x%x), ch[6](0x%x)\n", result, ch[7], ch[6]);
	return 0;
}
上面的程式就能輸出正確的答案result=0xfff,以下是程式執行的結果:

那為什麼這裡輸出的結果又是正確的呢?我們來看一下反彙編的結果:

	unsigned int result = (ch[7] << 8) + ch[6];
00411C68  movzx       eax,byte ptr [ebp-0Dh] 
00411C6C  shl         eax,8 
00411C6F  movzx       ecx,byte ptr [ebp-0Eh] 
00411C73  add         eax,ecx 
00411C75  mov         dword ptr [ebp-20h],eax 
由8位的無符號數轉換成32位無符號數使用了movzx(帶0擴充套件傳送指令)。在轉換的過程中沒有eax的第9至32位置為0,所以最後得到正確的結果。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

我們將第一個例子改一下,在計算result值的時候先將char型別的ch[7]和ch[6]強制轉換為unsigned char看一下最後得到結果會如何。下面是修改後的程式碼:

int main()
{
	char ch[12] = {0xF0, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00};
	unsigned int result = (((unsigned char) ch[7]) << 8) + ((unsigned char)ch[6]);
	printf("result(0x%x), ch[7](0x%x), ch[6](0x%x)\n", result, (unsigned char)ch[7], (unsigned char)ch[6]);
	return 0;
}
最後的結果就根第二個例子的結果一致,是我們想要的結果。我們再來看一下反彙編後的程式碼:
unsigned int result = (((unsigned char) ch[7]) << 8) + ((unsigned char)ch[6]);
00411C68  movzx       eax,byte ptr [ebp-0Dh] 
00411C6C  shl         eax,8 
00411C6F  movzx       ecx,byte ptr [ebp-0Eh] 
00411C73  add         eax,ecx 
00411C75  mov         dword ptr [ebp-20h],eax 
這段反彙編程式碼與第二個例子的反彙編碼一致。由於先將ch[7]強制轉為unsigned char,所以傳送指令使用了movzx。