天天看點

彙編中有符号與無符号數以及CF,OF标志位的區分

彙編中有符号與無符号數以及CF,OF标志位的區分

一、 隻有一個标準!

  首先需要知道,計算機對數值的存儲采用補碼形式存儲,一來避免了+0和-0的尴尬,二來數值的加法和減法可以統一為補碼的加法。

  在彙編語言層面,定義變量的時候,沒有 signed和unsignde 之分,彙編器統統将你輸入的整數字面量當作有符号數(最高位的符号位根據輸入的數值符号決定)處理成二進制補碼存入到計算機中,隻有這一個标準!

  彙編器不會區分有符号還是無符号然後用兩個标準來處理,它統統當作有符号的!并且統統彙編成補碼!也就是說,db -20 彙編後為:EC ,而 db 236 彙編後也為 EC 。這裡有一個小問題,思考深入的朋友會發現,db 是配置設定一個位元組,那麼一個位元組能表示的有符号整數範圍是:-128 ~ +127 ,那麼 db 236 超過了這一範圍,怎麼可以?是的,+236 的補碼的确超出了一個位元組的表示範圍,那麼拿兩個位元組(當然更多的位元組更好了)是可以裝下的,應為:00 EC,也就是說 +236的補碼應該是00 EC,一個位元組裝不下,但是,别忘了“截斷”這個概念,就是說最後的結果被截斷了,00 EC 是兩個位元組,被截斷成 EC ,是以,這是個“美麗的錯誤”,為什麼這麼說?因為,當你把 236 當作無符号數時,它彙編後的結果正好也是 EC ,這下皆大歡喜了,雖然彙編器隻用一個标準來處理,但是借用了“截斷”這個美麗的錯誤後,得到的結果是符合兩個标準的!也就是說,給你一個位元組,你想輸入有符号的數,比如 -20 那麼彙編後的結果是正确的;如果你輸入 236 那麼你肯定當作無符号數來處理了(因為236不在一個位元組能表示的有符号數的範圍内啊),得到的結果也是正确的。于是給大家一個錯覺:彙編器有兩套标準,會區分有符号和無符号,然後分别彙編。其實,你們被騙了。:-)

二、存在兩套指令!

  第一點說明彙編器隻用一個方法把整數字面量彙編成真正的機器數。但并不是說計算機不區分有符号數和無符号數,相反,計算機對有符号和無符号數區分的十厘清晰,因為計算機進行某些同樣功能的處理時有兩套指令作為後備,這就是分别為有符号和無符号數準備的。但是,這裡要強調一點,一個數到底是有符号數還是無符号數,計算機并不知道,這是由你來決定的,當你認為你要處理的數是有符号的,那麼你就用那一套處理有符号數的指令,當你認為你要處理的數是無符号的,那就用處理無符号數的那一套指令,計算機隻根據指令對每一位進行計算,并不在乎這個二進制資料是有符号還是無符号,這就需要依靠程式員判斷處理的數值是有符号還是無符号,僅此而已。

  加減法隻有一套指令,因為這一套指令同時适用于有符号和無符号(剛才所說的補碼優點)。而有些指令,如:mul div movzx … 是處理無符号數的,而這些:imul idiv movsx … 是處理有符号的(也就是針對有符号和無符号提供兩套指令)

舉例來說:

  我們在彙編中,向記憶體中放入兩個資料,一個位元組x 為:0x EC ,一個位元組 y 為:0x 02 。

  發現沒有負号,都是正數,OK那麼最高位的負号位設定為0,再對x進行截斷處理,得到

x:

1 1 1 0 1 1 0 0

y:

0 0 0 0 0 0 1 0

  注意這裡的x,如果x是個有符号數字,雖然我們給的是一個ec正數,但是由于截斷處理,最高位為1,成了一個負數,又由于這是補碼儲存,原碼就成了-20,y仍然為2

當作無符号數看時,第一位的符号位不看,x = 236 ,y = 2 。

  但是從計算機的角度看,這就是一串二進制,哪有什麼有符号無符号數字

  下面用 add 指令進行加法運算,計算機開始工作,它隻需要把每一位相加,僅此而已,它才不分什麼有符号,無符号。

結果:

1 1 1 0 1 1 1 0

  這個結果當作有符号數就是:-18 ,無符号數就是 238 。同樣,計算機認為這仍然是一串二進制,是以add 一個指令可以适用有符号和無符号兩種情況。(呵呵,其實為什麼要補碼啊,就是為了這個呗,:-))

  乘法運算就不行了,必須用兩套指令,有符号的情況下用imul 得到的結果是:0x FF D8 就是 -40 。無符号的情況下用 mul ,得到:0x 01 D8 就是 472 。

三、 OF、CF、SF标志

  為什麼說着三個标志位,因為我對彙編中無符号,有符号的疑惑就是從這三個标志位的學習而産生的。

  先看CF标志位,書上說CF标志位隻對無符号數有意義,首先明白一點,即使是兩個有符号數相加,也會導緻CF的變動,并不是說有符号數,編譯器不設定CF位。

因為CF的标志位的變動是由于最高有效位(如果對于8位數,就是第8位)向更高位(第9位)産生了進位或者借位而産生,而對于有符号數來說,最高位是符号位,它的變動和數值位的變動意義不一樣。是以對于有符号數,CF也可能發生變動,但是它的變動是沒意義的。而如果是無符号數,它的變動就意味中8位的記憶體或寄存器不足以儲存資料,因為資料産生了進位或借位。

  再看OF标志位,它隻對有符号數有意義,因為兩個标準的8位有符号資料(标準指的是指派的時候不要賦超過有符号數範圍的數字,由于截斷,即是8位能儲存,儲存進來的資料數值大小早就産生了變化),這2個資料隻有同号(都為正或為負)相加才會溢出,也就是結果超過有符号數的範圍。例如2個正數,符号位(第8位)都為0,相加後發生溢出,符号位由于第7位的進位變成了1,兩個正數相加變為了負數?由此對OF産生了作用,如此來說OF的作用是由于符号位發生變化,如果是兩個無符号數,最高位代表的并不是符号意義,産生了變動也是無意義的,是以說OF隻對有符号數有意義。

最後SF标志,有了上面的介紹,就能了解SF看的是最高位的符号位意義,對于無符号數來說,最高位代表的是數值意義,并不是符号意義。

四、可愛又可怕的c語言。

  為什麼又扯到 c 了?因為大多數遇到有符号還是無符号問題的朋友,都是c裡面的 signed 和 unsigned 聲明引起的,那為什麼開頭是從彙編講起呢?因為我們現在用的c編譯器,無論gcc 也好,vc6 的cl 也好,都是将c語言代碼編譯成彙編語言代碼,然後再用彙編器彙編成機器碼的。搞清楚了彙編,就相當于從根本上明白了c,而且,用機器的思維去考慮問題,必須用彙編。(我一般遇到什麼奇怪的c語言的問題都是把它編譯成彙編來看。)

  C 是可愛的,因為c符合kiss 原則,對機器的抽象程度剛剛好,讓我們即提高了思維層面(比彙編的機器層面人性化多了),又不至于離機器太遠 (像c# ,Java之類就太遠了)。當初K&R 版的c就是進階一點的彙編……:-)

  C又是可怕的,因為它把機器層面的所有的東西都反應了出來,像這個有沒有符号的問題就是一例(java就不存在這個問題,因為它被設計成所有的整數都是有符号的)。為了說明c的可怕特舉一例:

#include <stdio.h> 
#include <string.h> 

int main()
{
int x = ; 
char * str = "abcd"; 
int y = (x - strlen(str) ) / ;
//注:原作者這樣寫,編譯器可能會對其優化,直接使用右移移位指令而不是采用除法指令,改成3即可看到
printf("%d\n",y);
}
           

  結果應該是 -1 但是卻得到:2147483647 。為什麼?因為strlen的傳回值,類型是size_t,也就是unsigned int ,與 int 混合計算時,int類型被自動轉換為unsigned int了,結果自然出乎意料。。。

觀察編譯後的代碼,除法指令為 div ,意味無符号除法。解決辦法就是強制轉換,變成 int y = (int)(x - strlen(str) ) / 2; 強制向有符号方向轉換(編譯器預設正好相反),這樣一來,除法指令編譯成 idiv 了。我們知道,就是同樣狀态的兩個記憶體機關,用有符号處理指令 imul ,idiv 等得到的結果,與用 無符号處理指令mul,div等得到的結果,是截然不同的!是以牽扯到有符号無符号計算的問題,特别是存在讨厭的自動轉換時,要倍加小心!(這裡自動轉換時,無論gcc還是cl都不提示!!!)

為了避免這些錯誤,建議,凡是在運算的時候,確定你的變量都是 signed 的。(完)

部分内容轉載自:

http://blog.csdn.net/xuezhongfenfei/article/details/8351838

并對其内容進行修改、注釋和擴充,感謝原作者的分享

上一篇: 算法學習4
下一篇: lora的出路