1、原碼、反碼、補碼知識的復習:
三者的最高位均為符號位。我以前一直沒弄明白的是為何8位補碼的表示范圍是-128~127,今天查閱了相關資料,於此記下。
仍然以8位為例:
原碼的表示范圍:-127~-0,+0~+127,共256個數字。正0的原碼是0000 0000,負0的原碼是:1000 0000,有正0負0之分,不符合人的習慣,待解決。
反碼:除符號位,原碼其余位取反而得。+0:0000 0000,-0:1111 1111仍然有正0負0之分。
補碼:在反碼的基礎上加1而得。對原碼的兩種0同時末位加1。+0:0000 0000,-0:0000 0000(因為溢出導致8位全0)。消除了正0負0之別,如此一來,便節省出一個數值表示方式1000 0000,不能浪費,用來表示-128。-128特殊之處在於沒有相應的反碼原碼。也可以這樣考慮:
-1: 1111 1111
-2: 1111 1110(在-1的基礎上減1,直接將補碼減1即可)
-3: 1111 1101(在-2補碼基礎上減1,以下類似)
-4: 1111 1100
……
-127:1000 0001
-128:1000 0000
如此以來:8位補碼表示范圍是-128~+127因為0只有一種形式所以,仍然是256個數,若8位代表無符號數,則表示范圍是:0~255,這就是為什么高級語言講到數據類型,比如C++中的short類型時(16位長)說其表示范圍是:-32768~+32767,而unsigned short表示的范圍則是:0~65535
2、關於無符號數和有符號數:
無符號數及有符號數的定義就不多說了,任何計算機基礎書籍都會講到。
二者的區別:我們知道,有符號數在計算機中以補碼的形式存儲,無符號數其實就是正數,三碼一致,存儲形式即是其十進制真值對應的二進制數。所以可以這樣說,無論有符號數還是無符號數,都是以補碼(相對真值來說)的形式來存儲的,補碼在運算是符號位也會參與。
其實對計算機來說,它根本沒有所謂的無符號有符號這樣的約定機制,無符號有符號只不過是我們(程序員、學習者)看待二進制數據的方式,比如,對於16位的寄存器(比如ax)有符號數-1的存儲形式是0FFFFH(即16個1,-1的補碼,最高位符號位),而同時無符號數65535的存儲形式也是0FFFFH。所以對計算機來說,它僅僅是存儲了一串二進制,至於是有符號數還是無符號,你程序員要心中有數。C++代碼舉例:
short a=-1; unsigned short b=a; cout<<a<<endl;//打印有符號數-1,對應存儲形式0FFFFH cout<<b<<endl;//打印無符號數65535對應存儲形式0FFFFH
3、匯編相關
已經說過,數據以補碼形式存儲於計算機中(有符號、無符號),自然,寄存器,比如ax,存的也是補碼,比如:mov ax,-96,查看便知ax存儲的是0FA0H。在高級語言編程中,我們完全不用考慮什么原碼補碼。匯編的add指令在運算時,比如
mov al,0
add al,-1
它不去“考慮”什么有符號無符號,反正數據都以補碼(無符號數據便是正數,補碼不變)的形式存儲、運算,它只管將al中的補碼與99的補碼加一起然后存入al中(補碼的運算特點是:符號位也參與運算),我們知道運算后al存儲的是1111 1111(0FFH),至於你如何看待這個結果,由你程序員控制,你看成有符號數它便是-1,看成無符號數它便是255。
與此同時,相關的寄存器也會有相應的機制,來配合編程人員。比如標志寄存器中的SF標志位,如果編程人員將數據看成無符號數,則我們SF對我們是毫無意義的——盡管對我們毫無意義,但計算機依然以“有符號數”的態度來“看待”參與運算和存儲的數據,比如
mov ax,12
add ax,-18
結果為負,sf=1(結果如果為非負,則sf=0)
計算機不管編程人員以什么眼光(有符號無符號)來看待數據,反正它把所有的可能都考慮到了,把相應的工作也做了,編程人員可按需而來。不妨想象一種情景:假如計算機完全以無符號數來看待數據,壓根沒有sf標志位,那么如果程序員想知道某數據的正負(以有符號數來看待數據)時,便無從下手了。
可以說,計算機仁至義盡,剩下的便是程序員的事了。
4、匯編中的CF、OF、SF標志位及cmp操作
CF對無符號數運算是有意義的,OF,SF對有符號數而言是有意義的。ZF則都適用。
一個是進位借位標志位一個是溢出標志位,溢出是指:在進行有符號數運算時,結果超過了機器所有表示的范圍(五爽《匯編語言》),可知,OF只對有符數運算有意義,無符號數不關心這種溢出。無符號數關心的是最高位向更高位借位進位問題。(從這個層面上來講,其實也可將這種向更高位借位進位看成無符號數的“溢出”)。所以CF只對無符號數的運算有意義,即當考慮CF標志位的時候要做到心中有數:現在參與運算的都是無符號數——這依然是程序員的事。
可能會有這樣的疑問:兩個數運算,如果溢出了,那么以“無符號的眼光”看來,不也是產生了借位或者進位嗎?或者反之,兩個數運算,如果進位或者借位了,那么以”有符號的眼光“看來,不也是產生了溢出嗎?
對於第一個問題:不一定,比如0111 1111+0000 0001,看成有符號數運算的話,結果明顯溢出,但如果看作無符號的運算的話並沒有向更高位(無符號數無符號位,均為數值位)進位。
又如:mov al,98 add al,99(即0110 0010+0110 0011)運算后CF=0,OF=1。具體的過程可以這樣考慮:
首先,用的是al來運算,所以計算機知道做的是8位運算,加得結果:1100 0101,對於無符號來說無進位,CF=0,對於有符號來說溢出了:OF=1.
對於第二個問題:也不一定。如:
mov al,0F0H
add al,88H執行后CF=1,OF=1,但是,請看另一例:
mov al,0F0H
add al,78H
對於0F0H和78H,首先要明白,如果看作無符號數,則便是240+120,明顯會在高位向更高位產生進位,所以CF=1,如果看作有符號數,作為人類的程序員,有兩種方式可以判定是否溢出:
a、將0F0H和78H(補碼)轉換成10進制數值(按照符號位不變其余位取反然后末位加1轉成原碼,再求),相加看是否在-128~127之內。也可按補碼求真值的公式直接求出補碼對應的真值:真值x=-2^n*xn+其余各位2進制對應真值。其中xn為最高位即符號位,為0或者1。
b、單符號判溢出方法:這是組成原理(白中英主編第4版)介紹的判斷溢出的方法,補碼相加,看符號位及最高有效位。將16進制的補碼轉換成2進制:1111 0000和0111 1000,相加:
1111 0000
0111 1000
可看出最高有效位有符號位均產生了進位,這種情況不視為溢出(只有二者之一產生進位時才算溢出)。
有符號數的溢出問題引發了cmp操作的相關問題。
cmp是比較指令,相當於減法,但不會將結果保存在目的操作數中,但會影響標志位sf,zf,cf。利用它既可以對無符號數比較也可以對有符號數進行比較。至於是有符號數還是無符號數,這依然取決於我們的看待方式,如:
mov ax,8
mov bx,3
cmp ax,bx
看成無符號數分析:因為無須借位,cf=0(據此可以判斷ax>bx),因為結果不為0,zf=0(據此可以判斷ax bx是否相等)
對於有符號數,情況要復雜一些,因為牽扯到了溢出問題。
比如已知(ah)=22H,(bh)=0A0H,二者都是補碼,一個為正一個為負,二者對應的真值分別為:34和-96,邏輯上講(ah)>(bh),但cmp ah,bh運算時會溢出:34-(-96)=-230,小於-128。另外,我們可以直接用16進制減法手動計算結果:
22
A0
---------
82H,即1000 0010,知結果為-1*2^7+2=-126,這顯然是個錯誤的結果。計算機不會進行減法,它得轉成加法。用補碼加法計算有個好處,可用上邊已經介紹過的單符號位判溢出方法判斷結果是否溢出。過程:x補=22H=0010 0010,y補=1010 0000,求x補-y補,這是補碼減法,轉換成加法:x補-y補=x補+[-y]補,由y補求[-y]補:包括符號位全部取反,然后末位加1(參照白中英組成原理課本相關內容),轉變后即為:0010 0010+0110 0000,由單符號位判溢出方法可知,只有高位有效位產生了進位,進給了符號位,這是一個溢出,一個上溢。
要注意的是:引入補碼帶來的便處之一使得負數的加法轉成補碼的加法(負數的加法便是減法),但是如果不加控制補碼之間也會進行減法,此時也要將相應的減法轉成加法運算,當然這種轉換的工作不是我們所做的,轉換的原理如上所述。
因為溢出問題,導致上面cmp結果為負,sf=1,如果僅據此就說(ah)<(bh)明顯是錯誤的,此時還要結合of位,因為有溢出,所以of=1(如果of等於0,說明沒有溢出,則我們可以放心地使用sf來判斷)。一條規律是:有溢出,則cmp結果與of表示的相反,本來打算驗證一下,還是算了,抱着復習相關知識的心態,寫了這么多,蛋疼。