Ch 06 再談 ASCII 碼


在第三章時提到將 ASCII 碼印在銀幕上但是僅止於印出,畫面不好。這一章堭N改進這個程式,把 ASCII 碼所代表的字及其數值印出在銀幕上,同時為防止銀幕上捲速度太快,所以每次只顯示 20 個 ASCII 碼。

原始程式

我們先來看看這個程式的原始程式:
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
;***************************************
code    segment
        assume  cs:code,ds:code
        org     100h
;---------------------------------------
start:  sub     bx,bx   ;使 BX 等於零
 
next:   mov     dl,bl   ;此行及以下 3 行印出 BL 內容的
        mov     cl,4    ;較高的四個位元
        shr     dl,cl
        call    print
        mov     dl,bl   ;此行及以下 2 行印出 BL 內容的
        and     dl,0fh  ;較低的四個位元
        call    print
 
        mov     ah,2    ;此行及以下兩行印出空白
        mov     dl,' '
        int     21h
        mov     dl,bl   ;此行及以下兩行印出 BL 所代表的
        int     21h     ;ASCII 字元
        call    cr_lf   ;印出歸位及換行字元
 
        inc     bl      ;使 BL 為下一個 ASCII 字元
        mov     ch,20   ;設定除數
        mov     ax,bx   ;設定被除數
        div     ch
        or      ah,ah   ;若餘數為零,表示已經顯示 20 個字了
        jnz     remain
        int     16h     ;所以應該等使用者按下任意鍵再繼續
 
remain: cmp     bl,0    ;如果 BL=0,表示已經完成 256 個字了
        jne     next
 
        mov     ax,4c00h
        int     21h
;---------------------------------------
;print 副程式
;輸入:DL-由 0 到 F 的十六進位數
;輸出:在螢幕上印出 DL 內的 ASCII 碼
print   proc    near
        add     dl,30h  ;加上 30H
        cmp     dl,'9'  ;比較看看是否超過 39H
        jbe     ok      ;沒超過直接印出
        add     dl,7    ;若超過再加上 7
ok:     mov     ah,2
        int     21h     ;印出
        ret
print   endp
;---------------------------------------
cr_lf   proc    near
        mov     ah,2
        mov     dl,0dh
        int     21h
        mov     dl,0ah
        int     21h
        ret
cr_lf   endp
;---------------------------------------
code    ends
;***************************************
        end     start

把這個程式組譯連結後,在 MS-DOS 模式下執行,如下面圖片:

此程式會先把 ASCII 碼的數值印在螢幕最左邊,然後印出空白,再印出字元。每印出 20 行後,就會停止,使用者必須按任意一鍵,程式又再印出下 20 行。ASCII 前 20 個字元,有許多是控制字元,不是普通的字元,因此是印不出來的。例如 07H,是使電腦發出「嗶」一聲;0AH 是 line feed,意思是換到下一行;0DH 是歸位字元,意思是游標移到最左邊。底下來介紹幾個新指令吧。

SUB 指令

SUB就是減法指令,它的語法是:
sub     暫存器,暫存器
sub     暫存器,數字
sub     暫存器,變數名
sub     變數名,暫存器
sub     變數名,數字
SUB 的作用是把前面的暫存器或變數減去後面的暫存器、數字或變數,再把結果存到前面的暫存器或變數中,要注意的是,前面的暫存器或變數中的數值會改變。例如有一程式片段:
mov     ax,9
mov     bx,5
sub     ax,bx
執行最後一行時,是將 AX 之值減去 BX 之值,再存回到 AX,所以最後 AX 為 4,而 BX 仍為 5。要注意的是減數與被減數都必需同時為 16 位元或同時為 8 位元,也就是說 sub ax,bl 是不合法的。

DIV 指令

這是計算除法的指令。div 指令的後面可以接暫存器或變數,這個暫存器或變數代表除數,而被除數固定是 AX 暫存器擔任,計算之後的商放在 AL 暫存器中,餘數放在 AH 暫存器中。例如你想計算 21 除以 5,寫成程式如下:
mov     ax,21
mov     bl,5
div     bl
計算完後 BL 仍等於 5,AX 會變成 0104H,其中 AL 為 4 是商,AH 為一是餘數。

如果被除數超過 65535,除數超過 255 的話,就用 DX:AX 來代替被除數,計算結果商數會存放在 AX,餘數存放在 DX。例如要計算 80000d(即13880h) 除以 4000,程式如下:

mov     dx,1
mov     ax,3880h
mov     bx,4000
div     bx

要注意的是,把被除數填入 DX:AX 時,要用 16 進位,DX 是填入較高的 16 位元,AX 填入較低的 16 位元。結果是 AX=14h,DX=0。有關除法指令在第八章中再詳細介紹。

OR 指令

這個指令的語法是
or      暫存器,暫存器
or      暫存器,數字
or      暫存器,變數
or      變數,暫存器
or      變數,數字
這個指令是將暫存器或變數的內容和後面的數字作『或』運算,再將其結果存入該暫存器或變數中。所謂『或』運算是指有兩個條件,只要其中之一成立就可以了。例如老師說:『明天考試,題目只有兩題,第一題答對或第二題答對就及格。』所以如果你第一題對第二題錯,或第一題錯第二題對,或兩題都對,都算及格。在組合語言中,『1』表示成立(真),『0』表示不成立(偽),CPU 在做『或』運算時,先把要作『或』運算的兩數換算成二進位,只要相對應的位數有一個為『1』,結果就是一。例如有一個程式片段如下:
mov     ax,832h
or      ax,0ac3h
832h 變成二進位數是 0000 1000 0011 0010,而 0ac3h 的二進位數是 0000 1010 1100 0011,運算如下圖:
解說 OR 運算
在這個例子堙A做或運算的兩數中的第二、三、八、十、十二、十三、十四、十五個位元均為零,所以運算結果的這些位元也是零。而其他不是 AX 為一,就是 0ac3h 這個數的二進位為一,所以結果為一。

AH=0/INT 16H BIOS 服務中斷

INT 16H 是 BIOS 所提供的一個服務程式,類似 INT 21H,所不同的是前者是 BIOS 所提供的,後者是 DOS 提供的。BIOS 所提供的服務程式有鍵盤(INT 16H)、螢幕(INT 10H)、序列埠(INT 14H)等等。其中 INT 16H 是提供有關鍵盤的服務程式,當 AH=0 時執行此中斷服務程式,電腦會停下來,等你按一個鍵才會繼續,執行完後會將使用者所按的鍵傳回 AX 暫存器中,第八章再詳細解說。此刻只要知道,它是用來使電腦停下來,等使用者按任何一鍵的服務程式就可以了。

程式解說

在這個程式堙A我用 BL 暫存器當作記錄 ASCII 碼的『變數』,所以一開始就使得 BL 暫存器等於零,然後每印出一個 ASCII 字元,BL 之值就增加一,使得 BL 指到下一個 ASCII 字元來。或許你會問,為什麼不寫成 mov bl,0 而寫成 sub bx,bx 呢?有兩個理由, 從第一行到第二十一行,應該都能明白,第二十二行是為了印出『歸位』和『換行』兩個字元。歸位的意思是指游標回到螢幕最左邊,換行是指將游標移到下一行。ASCII 碼中的 0dh、0ah 就分別表示這兩個特殊的字,雖然我們看不到這個『字』,但是卻能看到它所展現的效果。這兩個字合用的結果就是使游標移到下一行的最左邊。

第二十三行到三十三行是為了美觀,而檢查是不是已經印出 20 個字了,如果恰好印到第 20、40、60 等字,此時一整個螢幕也差不多滿了,要等使用者按一鍵再繼續下面 20 個字元。而這些數剛好是 20 的倍數,所以除以 20 後得到的餘數為零,若餘數(餘數放在 AH 中)為零,就要等使用者按一個按鍵再繼續。像這樣檢查某個數,若符合條件就到某處去執行的想法,稱為『條件跳躍』,底下說明其使用方法,你也可以參考 註二

在這個程式媕邠d AH 是否為零,我用 or ah,ah 來做,如果 AH 為零,做或運算後的結果還是零,or 指令會將旗標暫存器的 零旗標設為一,表示運算結果為零。若 AH 不為零,則做或運算之後也不為零,or 指令會將零旗標設為零。我想這邊有許多人會被搞糊塗了,在這兒整理一下: 運算結果為零,零旗標設為一;運算結果非零,零旗標設為零。 你可以看 註二,有更好記得方法喔。

到此,程式只是比較而已,並沒有真正跳躍,下一行才執行跳躍動作。第二十九行的 jnz (註三) 指令則是看到零旗標不為一(運算結果不是零),就會跳到 remain 標記處繼續執行,這裡的運算結果是指 or ah,ah 那一行運算,而 AH 中存有除以 20 後的餘數。所以這一行的意思是說,如果不是第 20、40、60 等數字的話就跳到 remain 標記處執行;如果是的話就繼續下一行指令,這個指令就是等待使用者按一個鍵再繼續執行。

第三十二、第三十三行指令是檢查是否已經將所有的 ASCII 碼都印出來了?如果是的話那 BL 暫存器又會歸零,就可以結束程式了。

本章到此結束,不知你可明瞭了?最後回顧本章重點:


註一:結果為零,所以將『零旗標』設為一,換句話說,零旗標為一,表示『有零』,還記得電腦中用『一』代表『有』或『真』的意思,用零代表『沒有』或『偽』的意思。

註二:組合語言中的條件跳躍指令(就是像『若 A 大於 B 就跳到某處』)通常會有兩行程式,一個是用來比較並設好旗標,另一個是依據上面設好的旗標執行跳躍。事實上,初學者寫程式時那裡還記得零旗標為一表示什麼等等。所以我整理出一些簡單的規則,只要記得下面幾個簡單的規則(我是說不用花多少力氣就能記得不是說還得查閱書籍才能清楚),你不用去管零旗標或是其他旗標,一樣可以處理好跳躍指令的。

底下說明幾個例子吧。

第一個例子:

cmp     ax,bx
jl      x1

這相當於『若 AX 小於 BX,則跳到 x1 處』,第一行的意義就是『比較 AX、BX』,第二行是『若小於就跳到 x1 處』。看看底下的圖解吧。

圖解條件跳躍指令
第二個例子是:
sub     ah,ah
jz      x1

這相當於『若 AH 減 AH 等於零,就跳躍』,此處得記得 sub 指令好像比較指令,如果『相等』,或『相減後為零』就跳躍。如果真的寫出這個程式片段,事實上運算後一定會跳躍的,因為 AH 減 AH 一定等於零。

註三:jnz 的意思是『Jump if Not Zero』


回到首頁到第五章到第七章