-1>1?! unsigned int的世界不簡單
程式語言提供了很多的基本數據類型,比如char,int,float,double等等。在C和C++的世界中,還有一種類型,叫做無符號數據,修飾符位unsigned,比如今天要說的unsigned int。引入特殊的類型,一方面帶來了好處,一方面也留下了隱患。
一、有符號數與無符號數誰大誰小
上程式碼:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = -1;
unsigned int b = 1;
if(a > b)
printf("a > b, a = %d, b = %u\n", a, b);
else
printf("a <= b, a = %d, b = %u\n", a, b);
return 0;
}
結果為:
什麼?-1竟然大於1?從結果上看,的確是這樣的。為什麼從這樣呢?這樣從C++對同時包含有符號數與無符號數的表達式的處理說起。
二、C++底層怎麼處理的
當執行一個運算時(如這裡的a>b),如果它的一個運算數是有符號的而另一個數是無符號的,那麼C語言會隱式地將有符號參數強制轉換類型為無符號數,並假設這兩個數都是非負的,來執行這個運算。這種方法對於標準的算術運算來說並無多大差異,但是對於像小於「<」和大於「>」這樣的運算就可能產生非直觀的結果。
對應上面的例子,就是先把-1這個有符號數強制轉換成無符號數,再與1比較,並假設兩個數都是非負的。那麼-1轉換成無符號數是多少呢?在32位或者64位機器上,-1對應的無符號數是4 294 967 295,即32位的無符號數的最大值(UMax),所以if中的條件總是為真。
要想這段程式碼正常執行,我們需要怎麼辦呢?很簡單,把if語句改為if(a > (int)b)即可。這樣程式就會認為是兩個有符號數在進行比較,-1就不會隱式地轉換為無符號數而變成UMax。
可能你已經有一個問題,為什麼使用強制類型,把變數b的類型變成int程式就能正常,而-1轉換成無符號數為什麼會是4 294 967 295呢?這就得從整型數據在電腦中的表示和C語言對待強制類型轉換的方式說起。
我們知道,整數在電腦中通常是以補碼的形式存在的,而-1的補碼(用4個位元組儲存)為1111,1111,1111,1111。而C語言對於強制類型轉換是怎麼處理的呢?對大多數C語言的實現,處理同樣字長的有符號數和無符號數之間的相互轉換的一般規則是:數值可能會改變,但是位模式不變。也就是說,將unsigned int強制類型轉換成int,或將int轉換成unsigned int底層的位表示保持不變。
也就是說,即使是-1轉換成unsigned int之後,它在記憶體中的表示還是沒有改變,即1111,1111,1111,1111。我們知道在電腦的底層,數據是沒有類型可言的,所有的數據非0即1。數據類型只有在高層的應用程式才有意義,也就是說,同樣的儲存表示對於應用程式而言可能對應著不同的數據,例如1111,1111,1111,1111對於有符號數而言它表示-1,但對於無符號數而言,它表示UMax,但是它們的底層存儲都是一樣的。現在你應該明白為什麼-1轉換成無符號數之後,就成了UMax了吧。
三、查看數據的底層表示
上程式碼,裡面有個show_byte函數,可以把從指針start開始的len個位元組用16進位數的形式列印。
#include <stdio.h>
#include <stdlib.h>
void show_bytes(unsigned char *start, int len)
{
int i = 0;
for(; i < len; ++i)
printf(" %.2x", start[i]);
printf("\n");
}
int main()
{
int a = -1;
unsigned int b = 4294967295;
printf("a = %d, a = %u\n", a, a);
printf("b = %d, b = %u\n", b, b);
show_bytes((unsigned char*)&a, sizeof(int));
show_bytes((unsigned char*)&b, sizeof(unsigned int));
return 0;
}
結果為:
printf函數中,%u表示以無符號數十進位的形式輸出,%d表示以有符號十進位的形式輸出。通過show_bytes函數,我們可以看到,-1與4 294 967 295的底層表示是一樣的,它們的位全部都是全1,即每個位元組表示為ff。