汇编语言.IF、.ELSE、.ELSEIF、.ENDIF伪指令

.IF、.ELSE、.ELSEIF 和 .ENDIF 伪指令使得程序员易于对多分支逻辑进行编码。它们让汇编器在后台生成 CMP 和条件跳转指令,这些指令显示在输出列表文件中。语法如下所示:

.IF conditionl
    statements
[.ELSEIF condition2
    statements ]
[.ELSE
    statements ]
.ENDIF

方括号表示 .ELSEIF 和 .ELSE 是可选的,而 .IF 和 .ENDIF 则是必需的。condition(条件)是布尔表达式,使用与 C++Java 相同的运算符 ( 比如:<、>、== 和 !=)。表达式在运行时计算。下面的例子给出了一些有效的条件,使用的是 32 位寄存器和变量:

eax > 10000h
val1 <= 100
val2 == eax
val3 != ebx

下面的例子给出的是复合条件:

(eax > 0) && (eax > 10000h)
(val1 <= 100) || (val2 <= 100)
(val2 != ebx) && !CARRY?

下表列出了所有的关系和逻辑运算符。

运算符 说明
expr1 == expr2 若 expr1 等于 expr2,则返回“真”
expr1 != expr2 若 expr1 不等于 expr2,则返回“真”
expr1 > expr2 若 expr1 大于 expr2,则返回"真”
expr1 ≥ expr2 若 expr1 大于等于 expr2,则返回“真”
expr1 < expr2 若 expr1 小于 expr2,则返回“真”
expr1 ≤ expr2  若 expr1 小于等于 expr2,则返回“真”
!expr1  若 expr 为假,则返回“真”
expr1expr2 对 expr1 和 expr2 执行逻辑 AND 运算
expr1 || expr2 对 1xprl 和 expr2 执行逻辑 OR 运算
expr1 & expr2 对 expr1 和 expr2 执行按位 AND 运算
CARR1? 若进位标志位置 11则返回“真”
OVERFLOW ? 若溢出标志位置 1,则返回“真”
PARITY ? 若奇偶标志位置 1,则返回“真”
SIGN ? 若符号标志位置 1,则返回“真”
ZERO ?  若零标志位置 1,则返回“真”

在使用 MASM 条件伪指令之前,一定要彻底了解怎样用纯汇编语言实现条件分支指令。此外,在包含条件伪指令的程序汇编时,要查看列表文件以确认 MASM 生成的代码确实是编程者所需要的。

生成 ASM 代码

当使用如 .IF 和 .ELSE 一样的高级伪指令时,汇编器将为程序员编写代码。例如,编写一条 .IF 伪指令来比较 EAX 与变量 val1:
mov eax,6
.IF eax > val1
    mov result,1
.ENDIF
假设 val1 和 result 是 32 位无符号整数,当汇编器读到前述代码时,就将它们扩展为下述汇编语言指令,用 Visual Studio 调试器运行程序时可以查看这些指令,操作为:右键点击, 选择 Go To Disassembly。
    mov eax,6
    cmp eax,val1
    jbe @C0001            ;无符号数比较跳转
    mov result, 1
@C0001:
标号名 @C0001 由汇编器创建,这样可以确保同一个过程中的所有标号都具有唯一性。

要控制 MASM 生成代码是否显示在源列表文件中,可以在 Visual Studio 中配置 Project 的属性。步骤如下:在 Project 菜单中,选择 Project Properties,选择 Microsoft Macro Assembler,选择 Listing File,再设置 Enable Assembly Generated Code Listing 为 Yes。

有符号数和无符号数的比较

当使用 .IF 伪指令来比较数值时,必须认识到 MASM 是如何生成条件跳转的。如果比较包含了一个无符号变量,则在生成代码中插入一条无符号条件跳转指令。如下还是前面的例子,比较 EAX 和无符号双字变量 val1:
.data
val1 DWORD 5
result DWORD ?
.code
    mov eax,6
    .IF eax > val1
        mov result,1
    .ENDIF
汇编器用 JBE(无符号跳转)指令对其进行扩展:
mov eax,6
cmp eax,val1
    jbe @C0001             ;无符号比较跳转
    mov result,1
@C0001:

1) 有符号数比较

如果 .IF 伪指令比较的是有符号变量,则在生成代码中插入一条有符号条件跳转指令。例如,val2 为有符号双字:
.data
val2 SDWORD -1
result DWORD ?
.code
    mov eax,6
    .IF eax > val2
        mov result,1
    .ENDIF
因此,汇编器用 JLE 指令生成代码,即基于有符号比较的跳转:
    mov eax,6
    cmp eax,val2
    jle @C0001               ;有符号比较跳转
    mov result,1
@C0001:

2) 寄存器比较

那么,现在可能会有一个问题:如果是两个寄存器进行比较,情况又是怎样的?显然,汇编器无法确定寄存器中的数值是有符号的还是无符号的:
mov eax,6
mov ebx,val2
.IF eax > ebx
    mov result,1
.ENDIF
下面生成的代码表示汇编器将其默认为无符号数比较(注意使用的是 JBE 指令):
    mov eax, 6
    mov ebx,val2
    cmp eax, ebx
    jbe @C0001
    mov result,1
@C0001:

复合表达式

很多复合布尔表达式使用逻辑 OR 和 AND 运算符。用 .IF 伪指令时,符号 || 表示的是逻辑 OR 运算符:

.IF expression1 || expression2
    statements
.ENDIF

同样,符号 && 表示的是逻辑 AND 运算符:

.IF expression1 && expression2
    statements
.ENDIF

下面的程序示例中将使用逻辑 OR 运算符。

1) SetCursorPosition 示例

下例给出的 SetCursorPosition 过程,根据两个输入参数 DH 和 DL,执行范围检查。Y 坐标(DH)范围必须为 0〜24。X 坐标(DL)范围必须为 0〜79。不论发现哪个坐标超出范围,都显示一条错误消息:
SetCursorPosition PROC
; 设置光标位置
; 接收: DL = X坐标, DH = Y坐标
; 检查 DL 和 DH 的范围
; 返回:无
;------------------------------------------------
.data
BadXCoordMsg BYTE "X-Coordinate out of range!",0Dh,0Ah,0
BadYCoordMsg BYTE "Y-Coordinate out of range!",0Dh,0Ah,0
.code
    .IF (DL < 0) || (DL > 79)
       mov  edx,OFFSET BadXCoordMsg
       call WriteString
       jmp  quit
    .ENDIF
    .IF (DH < 0) || (DH > 24)
       mov  edx,OFFSET BadYCoordMsg
       call WriteString
       jmp  quit
    .ENDIF
    call Gotoxy

quit:
    ret
SetCursorPosition ENDP
MASM 对 SetCursorPosition 进行预处理时,生成代码如下:
.code
;.IF (dl < 0) || (dl > 79)
    cmp dl, OOOh
    jb @C0002
    cmp dl, 04Fh
    jbe @C0001
@C0002:
    mov edx,OFFSET BadXCoordMsg
    call WriteString
    jmp quit
;.ENDIF
@C0001:
;.IF (dh < 0) || (dh > 24)
    cmp dh, OOOh
    jb @COOO5
    cmp    dh, 018h
    jbe @C0004
@COOO5:
    mov edx,OFFSET BadYCoordMsg
    call WriteString
    jmp quit
;.ENDIF
@C0004:
    call Gotoxy
quit:
    ret

2) 大学注册示例

假设有一个大学生想要进行课程注册。现在用两个条件来决定该生是否能注册:第一个条件是学生的平均成绩,范围为 0〜400,其中 400 是可能的最高成绩;第二个条件是学生期望获得的学分。可以使用多分支结构,包括 .IF、.ELSEIF 和 .ENDIF。示例如下。
.data
TRUE = 1
FALSE = 0
gradeAverage  WORD 275    ; 要检查的数值
credits       WORD 12     ; 要检查的数值
OkToRegister  BYTE ?

.code
main PROC

    mov OkToRegister,FALSE

    .IF gradeAverage > 350
       mov OkToRegister,TRUE
    .ELSEIF (gradeAverage > 250) && (credits <= 16)
       mov OkToRegister,TRUE
    .ELSEIF (credits <= 12)
       mov OkToRegister,TRUE
    .ENDIF
汇编器生成的相应代码如下所示,用 Microsoft Visual Studio 调试器的 Dissassembly 窗口可以查看该表。(为了便于阅读,已经对其进行了一些整理。)
    mov byte ptr OkToRegister,FALSE
    cmp word ptr gradeAverage,350
    jbe @C0006
    mov byte ptr OkToRegister,TRUE
    jmp @C0008
@C0006:
    cmp word ptr gradeAverage,250
    jbe @C0009
    cmp word ptr credits,16
    ja  @COOO9
    mov byte ptr OkToRegister,TRUE
    jmp @C0008
@C0009:
    cmp word ptr credits,12
    ja  @C0008
    mov byte ptr OkToRegister,TRUE
@COOO8:
汇编程序时,如果使用 /Sg 命令行就可以在源列表文件中显示 MASM 生成代码。被定义常量的大小(如当前代码示例中的 TRUE 和 FALSE)为 32 位。所以,把一个常量送入 BYTE 类型地址时,MASM 会插入 BYTE PTR 运算符。