補一下CSAPP的筆記
計算機中整數的表示與編碼
計算機中的整數主要包括兩種:有符號數與無符號數。
無符號數的編碼
其中有符號數的表示方法與傳統二進制一致。
假設有一個整數數據類型有w位。我們可以將位向量寫成
在這個編碼中,每個xi都取值爲0或1。我們用一個函數來表示B2Uw來表示:
無符號數的編碼方式實際上與我們所知道的二進制編碼方式是一致的。唯一要注意的是無符號數的編碼具有唯一性,也就是說一個數字只能有一個無符號數編碼。這是因爲B2Uw是一個雙射。
有符號數的編碼
有符號數的編碼主要有三種方式:原碼、補碼與反碼。我曾經寫過一篇博客來進行探究,這裏不贅述。
關於補碼的由來和作用
需要說明的是:補碼也具有唯一性,原碼與反碼不具備這種性質,因爲0在原碼與反碼中有兩種解釋。
有符號數與無符號數之間的轉換
有符號數轉無符號數
C語言中提供了在不同數據類型中做強制類型轉換的方法,對於無符號整數與有符號整數之間的轉換方式,大多數系統上默認的是底層的位不變,由此我們能推出有符號數與無符號數之間的轉換。
關於這些的轉換的的過程和原理,在此不贅述。這裏直接給出公式:
一個數的編碼方式從無符號編碼(補碼)轉換爲有符號編碼後的數值公式爲:
如果有符號數的真值小於0那麼,把真值加上2w即爲其無符號真值,如果真值大於0,那麼不變。
我們用一段C語言代碼舉例:
#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
int main(void)
{
int i = -1;
unsigned int j = (unsigned int)i;
printf("%u\n", j);
printf("%u\n", UINT_MAX);
system("pause");
}
VS2017下的運行結果:
數據類型int的大小爲8字節,32位,把-1轉換成無符號數需要加上232,結果爲232-1,正好爲無符號數編碼的最大值,所以與UINT_MAX的值一致。
無符號數轉有符號數
直接給出公式:
C語言代碼測試實例:
#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
int main(void)
{
unsigned int i = UINT_MAX;
int j=(int)i;
printf("%d", j);
system("pause");
}
VS2017下的運行結果:
需要說明的是,在VS2017的環境下,上面兩個程序經過測試即使不使用強制類型轉換也可以得到正確的結果,其一是C語言中如果發現左右兩邊數據類型不一致會自動把數據往左邊的類型轉換,其二是,printf中的格式說明符也會自動執行類型轉換,這裏使用強制類型轉換只是爲了讓轉換看起來更加清晰。
無符號整數與有符號整數互相轉換可能遇到的問題
由於C語言對同時包含有符號和無符號數表達式的這種處理方式,出現了一些奇特的行爲。當執行一個運算時如果它的一個運算數是有符號的而另一個是無符號的,那麼C語言會隱式地把有符號參數強制類型轉換爲無符號,並假設這兩個數都是非負的。
對於這樣的關係運算符來說,它會導致非直觀的結果。我們同樣用一個C語言程序來作爲測試:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
printf("%d", -1 < 0U);
printf("%d",(unsigned)-1 > -2);
}
VS2017運行結果:
第一個表達式中,由於0是無符號數,所以-1默認變成無符號數,即爲232-1,這個數必然比0要大。所以第一個表達式爲假。
第二個表達式中,通過把-1強制轉換成無符號數,-1變爲232-1,-2變爲232-2,所以第二個表達式爲真。
擴展一個數字的位表示
有時我們會把一個佔用空間較小的數據類型轉換爲佔用空間較大的數據類型(如果把佔用空間較大的數據類型轉換爲佔用空間較小的數據類型,可能會丟失數據,我們一般不推薦這麼做)。
無符號數的零擴展
定義寬度爲w位的位向量:
和寬度爲w’的位向量:
其中w'>w。則:
要將一個無符號數轉換爲一個更大的無符號數數據類型,我們只要簡單的在前面加上足夠的0即可,這種運算被稱爲零擴展。
有符號數的符號拓展
定義寬度爲w位的位向量:
和寬度爲w’的位向量:
其中w'>w。則:
要將補碼數字轉換爲一個更大的數據類型,可以執行一個符號擴展(sign-extension),在前面添加最高有效位的值。
具體證明略。
值得注意的點:在C語言中,把類型不同、大小不同的兩個數據類型相互轉換,先改變數據類型的大小,然後在執行類型轉換。
比如說:在C語言中,把一個short類型的變量轉換爲unsigned類型的變量,我們要先把short類型的變量擴展到8個字節,然後再執行有符號數到無符號數的轉換。
截斷數字
一些特殊情況下,儘管這樣做會帶來風險,但我們仍然有時候會需要把一個高位的數據類轉換爲低位的數據類型,這時候我們就需要截斷這個數字。
無符號數的截斷
定義寬度爲w位的位向量:
而它截斷爲k位的結果爲:
令x=B2U_w(\vec x),x'=B2U_(\vec x'),則x'=x mod 2^k。
截斷爲k爲實際上就是對原數的真值用2^k取模。具體證明過程略。
有符號數的截斷
要理解有符號數的截斷,我們首先要明白,無論是有符號數還是無符號數真正區別他們的不是他們的真值,而是他們的編碼方式,實際上無論是有符號數,還是無符號數,在內存中都表示爲串二進制數,有了編碼對他們真值的解釋,他們才能表示不同的數據。
我們都知道,截斷實際上就是截去前面冗餘的位,只留下我們需要的位,既然無符號數和有符號數在內存中表示的方法實際上都是一串二進制數,我們爲什麼不可以把一個有符號數的位模式,看做是無符號數的編碼,用無符號數的方式將其截斷後得到的真值,再用把無符號數轉換爲有符號數,最終得到將有符號數階段的真值。
總而言之,有符號數編碼的截斷結果是:
有符號數和無符號數的使用建議
有符號數到無符號數的隱式的強制類型轉換,往往會隱藏許多難以發現的錯誤,由於這種強制類型轉換在代碼找那個沒有明確指示的情況下發生的,程序員們往往會忽略掉它的影響。
C語言中雖然沒有明確規定無符號數的編碼方式,但大多數系統和機器都默認使用補碼來表示。並且對於無符號數而言,在很多程序設計語言中,並沒有這個類型,原因很簡單,就是因爲有符號數轉換爲無符號數會帶來許多難以察覺的問題。比如JAVA中就不含有無符號類型。