Ch 04 十六進位數

在這一章裡,我想要說明如何把一個八位元暫存器中的十六進位數印在螢幕上。八位元的暫存器有 8 個位元,8 個位元可以表示兩位十六進位數。就像每班 48 人,這四十八是兩位數,而且是十進位的兩位數。


應先知道的事

假如在 DL 暫存器的值為 01(十進位或十六進位的一都是用 01 表示),想把 01 這兩個字印在螢幕上,假如你用第三章提到的方法,利用 DOS 中斷服務程式來做,而寫成像下面這樣:
mov     dl,01
mov     ah,2
int     21h
那就錯了,因為這樣只會印出 DL 內 ASCII 碼所代表的字元,也就是笑臉,並不會印出「0」、「1」兩個阿拉伯數字來。

如果想要在螢幕上印出 01 來,要分成兩個步驟,第一步先印出高位數「0」,第二步再印出低位數「1」,就好比我們平時寫十進位的「48」,一般先寫十位數「4」,再寫個位數「8」。我們得先查出 ASCII 碼代表「0」這個字元是 48(十六進位30H),而「1」是用 ASCII 碼的 49 表示。因此在第一步時要先將 DL 存入 48(亦即 30H),呼叫 AH=2/INT 21H 中斷印出來;第二步要在 DL 存入 49(亦即 31H),再呼叫 AH=2/INT21H 印出來。

暫存器中的數值是十六進位數,十六進位包含阿拉伯數字及英文字母,要想將十六進位數印出來還要注意下面的事情。阿拉伯數字「9」在 ASCII 碼是以 57d(39h) 表示,比 9 多一的數是十,十六進位中是以「A」表示,英文字母「A」在 ASCII 碼中是以 41h(65d)而不是 3Ah,兩者並不連續,所以超過 9 的還要再加上 7 ( 65d 減 57d ) 才能正確顯示出英文字母「A」來。

剛剛提過,八位元的暫存器,有兩位十六進位數,因此我們得先取出較高位的四個位元運算;再取出較低位的四個位元運算。


印出 BL 暫存器值的程式

我將印出 BL 暫存器之值的程式列在下面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
;***************************************
code    segment
        assume  cs:code,ds:code
        org     100h
 
;---------------------------------------
start:  mov     bl,2bh
;以下九行印出 BL 內較高的 4 個位元,例如 BL=2B 則在螢幕印出「2」
        mov     cl,4    ;將 4 存於 CL
        mov     dl,bl   ;將 BL 之內容存於 DL 中以方便印出
        shr     dl,cl   ;把 BL 較高之 4 位元變成 DL 中較低之 4 位元
        add     dl,30h  ;加上 30H
        cmp     dl,'9'  ;比較看看是否超過 39H
        jbe     ok_1    ;沒超過直接印出
        add     dl,7    ;若超過再加上 7
ok_1:   mov     ah,2
        int     21h     ;印出
 
;以下 8 行印出 BL 較低的 4 個位元,例如 BL=2B 則在螢幕印出「B」
        mov     dl,bl   ;將 BL 之值存入 DL
        and     dl,0fh  ;取得 DL 之較低的 4 個位元
        add     dl,30h  ;加上 30H
        cmp     dl,'9'  ;比較看看是否超過 9
        jbe     ok_2    ;沒超過直接印出
        add     dl,7    ;若超過再加上 7
ok_2:   mov     ah,2
        int     21h     ;印出
 
        mov     ax,4c00h;結束程式
        int     21h
;---------------------------------------
code    ends
        end     start

底下來介紹這個程式所用的新指令。

SHR 指令

第一個是 SHR,這個指令是將暫存器向右移數個位元,再將結果存回原暫存器,其語法如下:

        shr     暫存器,cl
或      shr     暫存器,數字
要位移的位元數可以直接以數字或事先存於 CL 暫存器。

舉例來說,假如 BL 暫存器內容為 2BH,則在 BL 暫存器內的每個位元如下圖:(若你不知為何 2BH 會變成下面一大串的 0 與 1,請看附錄一

底色為紅色的部分是較高的四個位元,底色為黑色的是較低的四個位元。

經過向右移一個位元後,BL 之值會變成

你會發現,BL 暫存器內的每個位元都向右移了一位,原來最右邊(即最低位元)的 1 被右移到暫存器外而被丟棄了,原來的右邊算來第二個位元變成最右邊的位元,其餘類推。而原來最左邊(最高位元)由暫存器外推了進一個 0 (底色為藍色的),可以把它想成暫存器外是空的,故移入的不是 1。這就是 SHR 指令的功用,如果連續再將 BL 的位元右移三次,就變成
換句話說,如果一個八位元的暫存器經過四次向右位移後,原來的較高的四個位元就變成較低的四個位元了。(注意到原本底色為紅色的部分移位了)

此外使用 8088/8086 CPU 時,如果要一次右移好幾個位元,應先將右移次數存到 CL 暫存器中,再用「SHR 暫存器,CL」指令,如果是只右移一位,可以直接用「SHR 暫存器,1」。如果用 80286 等級以上的 CPU,右移的位元數即使超過一,也可以用「SHR 暫存器,次數」來做。

ADD 指令

第二個指令是 ADD,就是加法指令,它的語法是:
add     暫存器,暫存器
add     暫存器,數字(常數)
add     暫存器,變數名
add     變數名,暫存器
add     變數名,數字
上面那行的作用是把後面的數字、變數或暫存器加到前面的變數或暫存器中,只有前面的變數或暫存器的內容會改變,後面的不變。例如有一程式片段:
mov     ax,3
mov     bx,5
add     ax,bx
執行最後一行時,是將 BX 之值和 AX 之值加起來,再存回到 AX,所以最後 AX 為 8,而 BX 仍為 5。
add     ax,ax
則會使 AX 之值變為兩倍。要注意的是加數與被加數都必需同時為 16 位元或同時為 8 位元,也就是說 add ax,bl 是不合法的。

CMP 和條件跳躍指令

第三個要介紹的指令是 CMP 和條件跳躍指令(包含 JA、JB、JAE、JBE 等等)。CMP 和條件跳躍指令常常合起來一起用,主要是用來判斷兩數大小,然後依條件跳到指定的地方繼續執行,有點像 BASIC 語言的 IF A > B THEN GOTO 200。以這程式為例,
cmp     dl,'9'
jbe     ok_1
意思就是先比較 DL 和 39H 之大小,若 DL 小於或等於 39H 的話,則跳到標記 ok_1 處繼續執行;否則就執行「jbe ok_1」的下一行指令。此處如果明白英文含義就很容易記住了,cmp 當然是 compare(比較的意思),jbe 的 j 是指 jump(跳躍),jbe 的 e 是指 equal(相等),b 是指小於,a 是指大於。因此 JA、JB、JAE 的意義分別就是若大於、小於、大於或等於就跳躍(註一)。此外,這四種跳躍指令,JA、JB、JAE、JBE,稱為條件跳躍,亦即要符合條件才跳躍,而且跳躍範圍不能超過 128 位元組,否則組譯器會出錯而無法組譯。

此外要注意的是 cmp dl,'9' 和 cmp dl,9 是完全不一樣的意義,前者有加引號的是指比較「9」這個字元,它所代表的是 ASCII 碼的 39H,在記憶體中它的數值是十六進位的 39H,因此 cmp dl,'9' 意思是 cmp dl,39h。而後者不加引號的就是表示數值 9,在記憶體中它的數值就是 9H。

AND 指令

第四個指令是 AND,其語法是:
and     暫存器,數字
and     暫存器,變數
and     暫存器,暫存器
and     變數,數字
and     變數,暫存器
這個指令是將暫存器的內容與後面的數字做 AND 運算,再將結果存入該暫存器或變數中。AND 翻譯成「且」的意思,這種運算是只有當兩個條件都成立時才為真。例如你放學回家,媽媽說:「要寫完今天的作業並且預習完明日的功課才能玩電腦。」意思就是如果你「今日作業」和「預習明日功課」只要有一項沒做完,就不能「玩電腦」,只有當兩項都做好了才可以玩電腦。在電腦中,「1」表示真或成立的意思,「0」表示偽或不成立的意思。所以,只有當兩個數都為一,做 AND 運算時,其結果才為一,只要運算的兩數有一個為零,結果就為零。假如 DL=2BH,執行 and dl,0fh 指令以下圖說明:
解說 AND 運算

我們看最右邊那位數 ( 也就是最低位數,一般稱為第 0 位元 ),不管是 DL 之值或是 0F 之值都是一,所以結果為一;最右邊第二位數也是一樣;最右邊第三位數,因為 DL 為零,只要有一個為零,所以結果就是零;其餘類推。

你可能會發現,如果我要得到某個八位元的暫存器(例如 AL 暫存器)最低的四個位元(右邊四個位元),並且使較高的四個位元為零,就用「AND AL,0FH」即可;相反的,如果我要得到較高的四個位元,且將較低的四個位元變成零,就用「AND 0F0H,AL」這種運算,很常在組合語言應用到。


程式說明

這程式從標記 start: 開始到標記 ok_1: 下一行是印出 BL 暫存器較高的四個位元,但為避免 BL 的內容會被破壞,故用 DL 來作運算,並且印出字的中斷服務程式是將 ASCII 碼存於 DL 中,所以用 DL 來運算又可以保存 BL。

然後用 SHR 指令將 DL 右移四個位元,再加上 30H 使 DL 的內容對應到 ASCII 碼的 30H 到 39H,這樣在螢幕上顯示的字元就是阿拉伯數字的「0」到「9」,但是若 DL 的數值超過 9 時,照上面的運算會得到 3AH,但 ASCII 碼 3AH 所代表的字並非對應到英文字母「A」,查表得知「A」的 ASCII 碼是 41H,所以如果超過 39H 的話,要先加上 7 再印出來。(為何加 7,想想看吧!)

程式其餘部分是將 BL 較低的四位元印在螢幕上,原理和上面差不多,就不再重複了。


結語

這個把 BL 暫存器之內容印在螢幕上的程式雖然簡單但是有些觀念不易瞭解,最好能將他由 DEBUG 載入,實際觀察在記憶體中如何運作,這樣做是很有幫助的,請有心學組合語言的人務必實地操作。

本章塈A應該學到的有:

本章的這個程式分兩部分,分別將較高的四個位元和較低的四個位元印出來,所以有許多部分是重複的,這些可以用「副程式」來簡化,下一章我們來談談副程式。


註一:JA、JB、JAE、JBE 中的 A 和 B 分別代表大於和小於之意,此處的大於小於是指正數之比較(或無號數之比較)。通常在組合語言中,暫存器或記憶體中數字的正負並不是用前面加上一個「-」來表示,而是最高位元為「1」來表示。但有時候最高位元雖為「1」,也可以是正數,端看程式設計者如何定義。
舉例來說,若 AL=1000 1111B,可以是「-113d」,也可以是「143d」,很奇怪是不是?


回到首頁到第三章到第五章