Ch 27 條件組譯

條件組譯是組譯器在組譯階段,依據所設定的條件,使組譯器組譯某一段程式,或不組譯某一段程式。一般而言,條件組譯常配合巨集使用,使得撰寫組合語言原始檔能有初步的結構化 (註一 )。底下小木偶使用條件組譯與巨集配合而寫成的一個簡單程式:EXAM05.ASM。

這個例子是先在螢幕上印出『計算:2593+8888 = 』字串,然後再印出結果來。這是一個是很簡單的例子,但是小木偶要用一個巨集來解決印出字串及數字這兩種不同的資料形態。方法就是在巨集中加入能自動判斷輸入的引數是字串或是數字的假指令。

這兩者的分別在高階語言是涇渭分明的,在組合語言中,這兩者都是以二進位數字表示,分別並不是那麼明顯。EXAM05.ASM 在處理文字與數字的程式碼當然也是不一樣的。假如資料是文字的話,只需列印即可,如果是數字的話,就得換成十進位再印出來。但是在組合語言堳傶灝u的判斷資料是文字抑或數字。

TYPE 運算子

小木偶的想法很簡單,用 TYPE 運算子這個假指令來判斷該資料是字串或是數字。TYPE 假指令的用法是:

TYPE    變數

TYPE 會根據變數的定義決定運算結果,假如變數是以『DB』定義,則結果為 1;以『DW』定義,結果為 2;以『DD』定義,結果為 4;以『DQ』定義,結果為 8;以『DT』定義,結果為 10。在一般高階語言堙A很少變數只佔一個位元組,而組合語言則不一定,但是小木偶在想不出其他更好的方法,只有用 TYPE 來判斷資料形態。組合語言堙A定義一個字串,常用『DB』,定義變數,常用『DW』或『DQ』,因此如果 TYPE 傳回來的值為 1,則引數應該是字串,反之則否。

IF - ELSE - ENDIF 條件組譯假指令

解決了判斷資料的形態之後,接下來就是依據資料形態決定組譯那一段原始程式,也就是這一章的主角,IF - ELSE - ENDIF 假指令,它的格式是

IF 判斷式
        敘述一
[ELSE
        敘述二]
ENDIF

假如判斷式為真,組譯器就會組譯『敘述或指令一』內的指令;假如判斷式為偽,組譯器會組譯『敘述二』內的指令。假如判斷式為偽時,並沒有指令需要執行,那也可以省略敘述二,省略時,必須由 ELSE 到敘述二為止的部份省略,ENDIF 是用來表示 IF 敘述結束的,是不可省略的。敘述一或敘述二可以是由很多指令或是敘述組成。

一般而言,判斷式大部分是兩個數值之比較,比較結果為真,則傳回 0FFFFH,比較結果為否,則傳回 0,組譯器依據 0FFFFH 或 0 來組譯那一個程式片段。而比較的兩個數值必須是在組譯階段就能夠確定大小的數值,因此不可以使用暫存器或變數,而像資料長度,或是位址都是可以拿來作為比較的數值。下表表示能用在判斷式的關係運算子:

運算子 實例說明
EQvar1 EQ var2 若 var1 等於 var2 時,為真
NEvar1 NE var2 若 var1 不等於 var2 時,為真
LTvar1 LT var2 若 var1 小於 var2 時,為真
LEvar1 LE var2 若 var1 小於或等於 var2 時,為真
GTvar1 GT var2 若 var1 大於 var2 時,為真
GEvar1 GE var2 若 var1 大於或等於 var2 時,為真
NOTNOT var 若 var 為偽時,為真
AND var1 AND var2 若 var1、var2 皆為真時,為真
ORvar1 OR var2 若 var1、var2 中有一為真時,為真
XOR var1 XOR var2 若 var1 為真且 var2 為偽,或
var1 為偽且 var2 為真時,為真
數值var 若 var 不為零時,為真

原始程式

底下就是 EXAM05.ASM 程式列表:

include mymac.inc     ;01 載入 MYMAC.INC 巨集程式庫
purge   display       ;02 除去 DISPLAY 巨集
        .286          ;03 使用 80286 指令集

print_number    macro       ;;05 此巨集用來把 DL 堛獐ぉ以 ASCII 字元
        add     dl,'0'      ;;06 方式印出於螢光幕,印出前 DL 應該在 0
        mov     ah,2        ;;07 到 9 之間。
        int     21h
endm                        ;;09 結束 print_number 巨集

print   macro   var         ;;11 print 巨集開始
        local   tmp_var,nxt	
if      (type var) eq 1     ;;13
code    segment para    public  'code'
        mov     dx,offset var
        mov     ah,9
        int     21h
code    ends
        exitm
endif                       ;;20
if      (type var) eq 2     ;;21
data    segment para    public  'data'
tmp_var dt      ?
data    ends
code    segment para    public  'code'
        fild    var
        fbstp   tmp_var
        mov     si,offset tmp_var+2
        mov     dl,[si]
        print_number        ;;30 呼叫 print_number 巨集
        mov     cx,2
nxt:    dec     si
        mov     dl,[si]
        shr     dl,4
        print_number
        mov     dl,[si]
        and     dl,0fh
        print_number
        loop    nxt
code    ends
        exitm
endif                           ;;42
endm                            ;;43 print 巨集結束

;***************************************
data    segment para    public  'data'
string  db      '計算: 2593 + 8888 = $'
sum     dw      2593+8888
data    ends
;***************************************
        initial
        print   string
        print   sum
        exit    0

        end     start

print 巨集在邏輯上可分成兩部分,判斷資料形態及依據資料形態如何處理這兩部份。就前者而言,第 13 行和第 21 行這兩行就是判斷引數之資料形態是字串抑或字組整數;就後者而言,假如是字串的話,組譯器將組譯第 14 行到第 20 行,假如是字組整數的話,組譯器將組譯第 22 到第 41 行。

在 MASM 5.0 及其以後的版本,一個巨集堶惇O可以再使用另一個巨集,像這種,巨集裡面又有巨集的情形稱為『巢狀』,像第 30 行、第 35 行及第 38 行都是在 print 巨集埵A使用一個巨集,這是可以被允許的。MASM 並沒有限制巢狀巨集的層數,只要記憶體及堆疊不被使用完即可。


其他條件組譯指令

IFE 假指令

MASM 所提供的條件組譯敘述,除了 IF - ELSE - ENDIF 之外,還有好幾個,它們都可以配合 ELSE 使用,並且似乎『成雙成對』。小木偶的意思是,IF 是當條件為真時,組譯 IF 之後的敘述或指令,而還有一個 IFE 假指令與之配對,IFE 是指當條件為偽時,組譯 IFE 之後的敘述或指令。

IF1 與 IF2 假指令

IF1、IF2 是測試目前的組譯步驟。MASM 是兩階段 ( 註二 )的組譯器,IF1 與 IF2 就是分別只在第一階段組譯或第二階段組譯才組譯的條件組譯假指令。一般而言,巨集只需組譯一次,所以可以用 IF1 來增快組譯速度。這兩個假指令的語法是:

IF1
        敘述1
[ELSE
        敘述2]
ENDIF

IF2 也是一樣,都不需要測試條件,因為都已經寫在 IF 之後了。

IFDEF 與 IFNDEF 假指令

IFDEF 假指令是用來測試其後的變數或標記等符號是否經過定義,如果是的話才組譯;而 IFNDEF 則是未定義才組譯。其語法是:

IFDEF   符號名
        敘述1
[ELSE
        敘述2]
ENDIF

但這個指令卻有令人不解的地方。假如某個符號在 IFDEF 之後才定義,在第一階段組譯 ( 註二 ) 時,當然是還未定義,但第二階段組譯時就是已定義了;又如果該符號在 IFDEF 之前就已經定義了,不管第一階段或第二階段組譯都是已定義,所以照這樣看來,似乎都得組譯 IFDEF 之後的敘述了,這樣 IFDEF、IFNDEF 豈不是根本就毫無用處?

原來要使用 IFDEF 或 IFNDEF 有兩個方法可供使用,一是配合前面的 IF1 或 IF2 使用,另一種方法是根本就不要在原始程式中定義該符號,等到要使用時,再於 DOS 命令提示下輸入 MASM 的參數『/D』來定義該變數,例如底下這個程式,SUM02.ASM,是計算由一開始,公差為一的等差數列之和,至於最末一項是什麼,則是由『/D』參數後面的定義來決定,如果沒以『/D』參數定義最末項,則設為 100。

last_number     macro   ;;01 是否定義最後一數
ifdef   number
n       dw      number  ;;02 是,則以選項定義為準
else
n       dw      100     ;;03 否,則加到 100
endif
endm

;***************************************
code    segment
        assume  cs:code,ds:code
        org     100h
;---------------------------------------
start:  jmp     short begin
        last_number             ;15 定義最後一項之值
counter dw      1               ;16 計數器
string  db      "1+2+...+$"     ;17 印出的字串
begin:  sub     bx,bx           ;18 BX 為和
        mov     cx,n            ;19 CX 為項數
next:   add     bx,counter      ;20 相加迴圈
        inc     counter
        loop    next

        mov     dx,offset string
        mov     ah,9
        int     21h
        push    bx              ;27 保存和
        mov     ax,n            ;28 印出最後一項
        call    display_ax
        mov     dl,'='          ;30 印出等號
        mov     ah,2
        int     21h

        pop     ax              ;34 印出和
        call    display_ax
        mov     ax,4c00h        ;36 結束
        int     21h
;---------------------------------------
;39 AL 之數值為十進位之個位數,此副程式將加上 30h
;40 使之成為 ASCII 字元,印出螢幕上
display_decimal proc    near
        cmp     n_zero,0        ;42 檢查
        jnz     dply
        or      al,al           ;44 檢查最高位數是否為零
        jz      exit            ;45 若是,則不印出來
        or      n_zero,1        ;46 若否,則印出來並且使 n_zero 設為一
dply:   push    ax
        push    dx
        mov     dl,al
        add     dl,'0'
        mov     ah,2
        int     21h
        pop     dx
        pop     ax
exit:   ret
n_zero  db      0               ;56 n_zero 為一個旗標,若是最高位數為
display_decimal endp            ;57 零則為一,依次遞減直到最高位數不為
;-------------------------------;58 零時,n_zero 才設為一
;59 把 AX 內的十六進位數值,以十進位方式印在螢幕上
display_ax      proc    near
        sub     dx,dx
        mov     bx,10000
        mov     n_zero,dl
        div     bx
        call    display_decimal
        mov     ax,dx
        mov     bx,1000
        sub     dx,dx
        div     bx
        call    display_decimal
        mov     ax,dx
        mov     bl,100
        div     bl
        mov     dl,al
        call    display_decimal
        mov     al,ah
        cbw
        mov     bl,10
        div     bl
        mov     dl,al
        call    display_decimal
        mov     dl,ah
        add     dl,'0'
        mov     ah,2
        int     21h
        ret
display_ax      endp
;---------------------------------------
code    ends
;***************************************
        end     start

組譯 SUM02.ASM 可以像以前一樣直接於 DOS 提示號下『MASM SUM02;』即可,這樣的話執行結果會是『1+2+...+100=5050』。但您也可以指定末項為其他數,執行結果會不同喔。例如:

H:\HomePage\SOURCE>masm /Dnumber=200 sum02; [Enter]
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.


  51502 + 418690 Bytes symbol space free

      0 Warning Errors
      0 Severe  Errors

H:\HomePage\SOURCE>link sum02; [Enter]

Microsoft (R) Personal Computer Linker  Version 2.40
Copyright (C) Microsoft Corp 1983, 1984, 1985.  All rights reserved.

Warning: no stack segment

H:\HomePage\SOURCE>exe2bin sum02 sum02.com [Enter]

H:\HomePage\SOURCE>sum02 [Enter]
1+2+...+200=20100
H:\HomePage\SOURCE>

注意到 MASM 組譯器白色部分的參數選項,改變其值就會造出不同的執行檔來。

IFB 與 IFNB 假指令

這兩個假指令的語法是:

IFB     <引數>
IFNB    <引數>

IFB 是用來測試是否有引數傳到巨集中,如果沒有引數的話 ( B 是指空白,blank,的意思,即沒有引數 ),則組譯。而 IFNB 則是有引數 ( NB 是指不空白,即有引數 ),則組譯。這樣說,您可能還是不懂,待小木偶舉個例子吧,底下這個巨集,push_reg,可以把好幾個暫存器推入堆疊,直到沒有指定的暫存器可推入,而推入堆疊的暫存器數目可以不固定且可以是任意十六位元的暫存器。

push_reg        MACRO   reg_string
        IRP     reg,<reg_string>
IFNB    <reg>
        push    reg
ENDIF
        ENDM
        ENDM

使用這個巨集時,輸入之參數必須以角括號包圍起來,例如在程式中用

        push_reg        <ax,bx,si>

來使用此巨集,因為角括號的關係,MASM 會把輸入的 ax,bx,si 當做一個字串傳入 push_reg 巨集,巨集的主要內容是一個不定重複區塊,該不定重複區塊的引數就是剛剛傳入巨集的字串,而後依次取出一個暫存器推入堆疊,直到暫存器都被提出為止。如何檢查暫存器全都被提出了呢?就是用 IFNB 來檢查,當還有暫存器未被提出時,IFNB 為真,組譯 push reg 這一行,若為偽時,則組譯 exitm,就跳出巨集了。

IFIDN 和 IFDIF 假指令

其語法是

IFIDN   <引數1>,<引數2>
IFDIF   <引數1>,<引數2>

IFIDN 是用來比較引數1 和引數2 是否相同,IDN 是 identical 之意,假如相同則組譯。IFDIF 則是用來比較引數1 和引數2 是否不同,DIF 是 different 之意,假如不同則組譯。這些引數都必須用角括號包住,並以『,』隔開。

IFIDN 和 IFDIF 比較時,會考慮英文字母的大小寫,意思是,AX 和 ax 被視為不同的字串;如果要忽略大小寫,則可以用 IFIDNI 和 IFDIDI,這最後的 I 字母表示忽略之意。


範例:通用的推入堆疊巨集

8086 指令的 PUSH 只能把十六位元的暫存器或十六位元變數推入堆疊,不能把十六位元立即值 (常數) 或八位元的暫存器推入堆疊,而底下這個巨集範例,push_op,也能使立即值或八位元的暫存器推入堆疊。底下是 push_op 原始碼:

        page    ,132            ;01

push_op MACRO   arg             ;;03

reg16   =       0               ;;05
reg08   =       0               ;;06
addr    =       0               ;;07

;;09 檢查輸入參數是否為 16 位元的暫存器
IRP     reg,<AX,BX,CX,DX,CS,DS,ES,SS,SI,DI,BP,SP,ax,bx,cx,dx,cs,ds,es,ss,si,di,bp,sp>
  IFIDN <reg>,<arg>
        push    arg             ;;12 如果相等的話,推入堆疊
        reg16   =       0ffffh  ;;13 數定虛擬變數為真
        exitm                   ;;14 跳出 IRP 區塊
  ENDIF
ENDM
IF      reg16                   ;;17 若 reg16 為真
        exitm                   ;;18 則跳出 push_op 巨集
ENDIF

;;21 檢查輸入參數是否為 16 位元的暫存器
IRP     reg,<aX,bX,cX,dX,cS,dS,eS,sS,sI,dI,bP,sP,Ax,Bx,Cx,Dx,Cs,Ds,Es,Ss,Si,Di,Bp,Sp>
  IFIDN <reg>,<arg>
        push    arg
        reg16   =       0ffffh
        exitm
  ENDIF
ENDM
IF      reg16
        exitm
ENDIF

;;33 檢查輸入參數是否為 8 位元的暫存器
IRP     reg,<al,bl,cl,dl,ah,bh,ch,dh,AH,BH,CH,DH,AL,BL,CL,DL>
  IFIDN <reg>,<arg>
        reg08   =       0ffffh
        exitm
  ENDIF
ENDM
IF      reg08
  IRPC  char,arg                ;;41 取得暫存器名的第一個字母
        push    char&&x         ;;42 推入堆疊
        exitm                   ;;43 跳出 IRPC 區塊
  ENDM
        exitm                   ;;45 跳出 push_op 巨集
ENDIF

;;48 檢查輸入參數是否為 8 位元的暫存器
IRP     reg,<Al,Bl,Cl,Dl,Ah,Bh,Ch,Dh,aL,bL,cL,dL,aH,bH,cH,dH>
  IFIDN <reg>,<arg>
        reg08   =       0ffffh
  ENDIF
ENDM
IF      reg08
  IRPC  char,arg
        push    char&&x
        exitm
  ENDM
        exitm
ENDIF

;;62 檢查輸入參數是否為含有暫存器間接定址模式,即 [BX]、[SI]……等等
IRPC    char,arg
  IF    ('&char' eq '[')
        addr=0ffffh
        exitm
  ENDIF
ENDM
IF      addr
        push    arg
        exitm
ENDIF

arg_size=((type arg)+1)/2       ;;74 輸入參數之長度
arg_type=(.type arg) and 3      ;;75 輸入參數之型態

;;77 檢查輸入參數是否為變數
IF      arg_type eq 2
  arg_offset  =0
  REPT  arg_size
        arg_address=word ptr arg+arg_offset
        push    arg_address
        arg_offset=arg_offset+2
  ENDM
        exitm
ENDIF

;;88 檢查輸入參數是否為標記
IF    arg_type eq 1
        push    bp
        mov     bp,sp
        push    ax
        mov     ax,offset arg
        xchg    ax,[bp]
        mov     bp,ax
        pop     ax
      exitm
;;98 若不是暫存器、定址模式、變數、標記的話,應為立即值
ELSE
        push    bp
        mov     bp,sp
        push    ax
        mov     ax,arg
        xchg    ax,[bp]
        mov     bp,ax
        pop     ax
ENDIF

ENDM

這個巨集結構很明顯,先檢查要推入堆疊的參數是否為 16 位元暫存器( 第 9 行到第 31 行 ),如果不是再檢查是否為 8 位元暫存器 ( 第 33 行到第 60 行 ),如果不是暫存器的話,再檢查是否為暫存器間接定址 ( 第 62 行到第 72 行 ),如果不是以上這幾種的話,再檢查是否推入變數到堆疊 ( 第 77 行到第 87 行 ),接下來檢查是否推入標記到堆疊 ( 第 88 行到第 97 行 ),假如都不是上述情形的話,就是推入立即值到堆疊了 ( 第 98 行到第 107 行 )。

檢查是否為暫存器的方法是用不定重複區塊 ( IRP ) 來指定要比較的範圍,故引數列 ( 即第 10 行角括號內的引數 ) 包含所有 16 位元暫存器名稱,但是因為參數與引數都被視為字串,所以大小寫是有差別的,必須在引數列堨]含不同的大小寫排列方式。指定好比較範圍後,再用 IFIDN 比較輸入參數是否為引數列中的一個,假如是 16 位元暫存器的話,則直接把該參數推入堆疊即可,並設定一個虛擬變數,reg16,為 0ffffh,0ffffh 表示真的意思 ( 第 12、13 行 )。然後再跳出 push_op 堆疊。

小木偶再把 IRP 重複區塊的執行方式說明一遍。第 10 行到第 16 行程式碼為:

IRP     reg,<AX,BX,CX,DX,CS,DS,ES,SS,SI,DI,BP,SP,ax,bx,cx,dx,cs,ds,es,ss,si,di,bp,sp>
  IFIDN <reg>,<arg>
        push    arg             ;;12 如果相等的話,推入堆疊
        reg16   =       0ffffh  ;;13 數定虛擬變數為真
        exitm                   ;;14 跳出 IRP 區塊
  ENDIF
ENDM
IF      reg16                   ;;17 若 reg16 為真
        exitm                   ;;18 則跳出 push_op 巨集
ENDIF

表示在第 10 行到第 16 行程式碼會重複組譯。第一次時,reg 會以 AX 代入組譯,第 11 行是比較 arg 與 reg 這兩字串是否相等,如果相等則組譯第 12 行到第 14 行之間的程式碼,不相等則結束 IFIDN,然後遇到 ENDM,故重複第二次,使 reg 以 BX 代入組譯……一直到 sp 所有引數結束。

第 14 行,是因為假如已經找到相符合的暫存器,就沒必要再比較了,這樣可以加快組譯速度。( 雖然也沒快多少。) 第 17 行到第 19 行,也是這樣的道理,既已找到是把暫存器推入堆疊,也就沒必要組譯以下的程式了,故直接跳出 push_op 堆疊。

您可能會問,第 14 行就已有了 exitm,為何第 18 行還要有個 exitm 呢?這是因為 RPT、IRP、IRPC 這三個重複區塊,類似巨集結構,若要在中間停止組譯都可以用 exitm 來跳出巨集或區塊,所以第 14 行是跳出 IRP 區塊,第 17 行是跳出 push_op 巨集。

第 33 行到第 61 行,是檢查參數是否為 8 位元暫存器,方法和上述幾乎相同,差別在於 8 位元暫存器 ( 例如 ah ) 無法推入堆疊必須改成 16 位元暫存器 ( 例如 ax )。所以第 41 行到第 44 行多了個 IRPC 重複區塊,此重複區塊是為了取得暫存器的第一個字母,當取得第一個字母就把該字母加上『x』再推入堆疊,然後跳出 IRPC 區塊及 push_op 巨集。至於該 IRPC 重複區塊的運作方式如下:該 IRPC 區塊重複次數只有兩次,分別以 8 位元暫存器名稱的兩個字母代入組譯,當第一次時即以 8 位元暫存器名稱的第一個字母代入,然後加上『x』再推入堆疊,然後立刻跳出 IRPC 區塊,故事實上這個重複區塊只組譯一次而已。

第 42 行的『&&x』為何要有兩個『&』呢?這是因為根據 MASM 手冊上說每層區塊要使用『&』,故第二層要用兩個『&』。

第 62 行到第 73 行是用來檢查推入堆疊的參數是否為暫存器間接定址,暫存器間接定址模式是像底下的樣子:

mov     ax,[bx]
push    [si]
sub     ax,[bx+200h]

觀察以上幾個例子,您會發現,這種定址模式含有兩個中括號,因此檢驗方法就是以 IRPC 檢查參數中是否有『[』( 第 64 行到第 67 行的 IF 條件組譯 ),假如有的話,會使虛擬變數,addr,設為真。然後接下來的就直接使該參數推入堆疊,因為 PUSH 指令就可以直接推入暫存器間接定址模式。

接下來就只剩下變數、標記與立即值未處理,要區別前兩者可用 MASM 所提供的 .TYPE 運算子。

.TYPE 運算子

.TYPE 和 TYPE 不同,TYPE 已在稍前說明過了,這兒小木偶只說明 .TYPE 的用法:( .TYPE 前有個小數點,不可省略 )

.TYPE   運算式

.TYPE 運算會根據運算式傳回一個位元組大小的資料,假如運算式不合法,則傳回零;如果合法,所傳回的位元組只有第 0、1、5、7 位元有意義,其他位元均為零,這四個位元所代表的意義如下表:

位元  該位元為零      該位元為一        說明
------------------------------------------------------------
 0    與程式無關      與程式有關    與程式有關是指標記……等
 1    與資料無關      與資料有關    與資料有關是指變數……等
 5    未定義          已定義
 7    區域性或公共性  外部的

當第 75 行的虛擬變數,arg_type,為 2 時,表示為變數,由第 79 行到第 85 行的程式處理;若 arg_type 為 1 時,表示為標記,由第 90 到第 97 行的程式處理;若不為 1 也不為 2,表示為立即值,由第 100 行到第 106 行的程式處理。

處理變數時,不只要能處理字組變數,也為了要能處理雙字組、四字組等型態的變數,帘狴H先取得變數長度,再除以 2,就能求出推入堆疊的次數,而每次推入堆疊時位址都得增加 2,這些細節都可在第 79 行到第 85 行 IF-ENDIF 之間的程式處理。

處理立即值的方式很特別,小木偶為了只把立即值推入堆疊,並且使所有暫存器都不改變,,當然只有 SP 暫存器會因為推入了一個立即值而減少二。為了達到上面的目的,寫了第 100 行到第 106 行的程式,雖然有點兒複雜,但應該不太難懂。處理堆疊其實就是這樣。而處理標記的方法和立即值相似,因為標記其實就是一個立即值,他表示程式位址。


感想

小木偶想,第 26、27 兩章的內容,巨集與條件組譯,應該可以使您對巨集只能單單減少打字的刻板映像有所改變。假如您能發揮一些想像力,巨集與條件組譯寫出來的組合語言程式可一點都不像組合語言呢!


註一:

古早以前,寫程式,尤其是利用 BASIC 撰寫的程式,常常因為條件跳躍(IF-ELSE-THEN)即無條件跳躍(GOTO)使得原始程式被切割成支離破碎,很不易維護。因此後來有許多程式設計師,不再濫用 GOTO 指令,遇到條件跳躍時,使條件為真兒要執行的指令包含在一個區塊中,不用執行的指令包含在另一個區塊中,並大量用副程式,這樣就使得原始程式較易維護。PASCAL、C、C++ 這些語言就是屬於結構化的語言。


註二:

MASM 組譯原始檔時,是分兩次組譯的 ( two pass ),要這麼做的原因是這樣的,請看以下說明。當 MASM 開始組譯時先讀入原始檔案,由上而下組譯,如果遇到尚未定義的標記、變數等,MASM 會先假設,而預留下一些空間給這些未定義的資料,當讀到原始程式後面時,MASM 發現這些未定義的標記、變數在後面定義,於是當第二次組譯時,再把這些先前假設的位址或長度修改成正確的數值。

當然如果先前假設的正確就沒有問題,如果假設錯誤的話,可以分為兩種情形。第一種情形是 MASM 所假設的空間或長度比所定義的來得大或多,那第二階段組譯時,MASM 會把多餘的空間以 NOP 指令取代。假如所假設的空間或長度比所定義的來得小或少的話,那就會產生錯誤,這就是所謂的『相位錯誤』(Phase error between passes)。

NOP 指令

這是一個 8086 指令集的其中一個指令,它的功用只是讓 CPU 空轉一個時脈,並不做任何事。


回到首頁到第二十六章到第二十八章