本章向程序员的汇编语言工具箱中引入一个重要的内容,使得编写出来的程序具备作决策的功能。几乎所有的程序都需要这种能力。首先,介绍布尔操作,由于能影响CPU状态标志,它们是所有条件指令的核心。然后,说明怎样使用演绎CPU状态标志的条件跳转和循环指令。接着演示如何用本章介绍的工具来实现理论计算机科学中最根本的结构之一:有限状态机。本章最后展示的是MASM内置的32位编程的逻辑结构。
6.1 条件分支
允许作决策的编程语言使得程序员可以改变控制流,使用的技术被称为条件分支。高级语言中的每一个IF 语句、switch 语句和条件循环都内置有分支逻辑。汇编语言,虽然是低级语言,但提供了决策逻辑所需的所有工具。本章将了解如何实现这种从高级条件语句到低级实现代码的转化。
处理硬件设备的程序必须要能够控制数字的单个位。每一个位都要被测试、清除和置位。数据加密和压缩也要依靠位操作。本章将展示如何在汇编语言中实现这些操作。
本章将回答一些基本问题:
●怎样使用第1章介绍的布尔操作(AND、OR、NOT)?
●怎样用汇编语言写IF语句?
●编译器如何将嵌套 IF 语句翻译为机器语言?
●如何清除和置位二进制数中的单个位?
●怎样实现简单的二进制数据加密?
●在布尔表达式中,如何区分有符号数和无符号数?
本章遵循自底而上的方法,以编程逻辑的二进制基础为开端。然后,说明 CPU 怎样用CMP 指令和处理器状态标志来比较指令操作数。最后,将这些内容综合起来,展示如何用汇编语言实现高级语言的逻辑结构特征。
6.2 布尔和比较指令
第1章介绍了四种基本的布尔代数操作:AND、OR、XOR 和 NOT。用汇编语言指令,这些操作可以在二进制位上实现。同样,这些操作在布尔表达式层次上也很重要,比如 IF语句。首先了解按位指令,这里使用的技术也可以用于操作硬件设备控制位,实现通信协议以及加密数据,这里只列举了几种应用。Intel指令集包含了AND、OR、XOR和NOT指令,它们能直接在二进制位上实现布尔操作,如表 6-1 所示。此外,TEST 指令是一种非破坏性的 AND 操作。
表6-1 部分布尔指令 | |
操作 | 说明 |
AND | 源操作数和目的操作数进行逻辑与操作 |
OR | 源操作数和目的操作数进行逻辑或操作 |
XOR | 源操作数和目的操作数进行逻辑异或操作 |
NOT | 对目标操作数进行逻辑非操作 |
TEST | 源操作数和目的操作数进行逻辑与操作,并适当地设置CPU标志位 |
6.2.1 CPU状态标志
布尔指令影响零标志位、进位标志位、符号标志位、溢出标志位和奇偶标志位。下面简单回顾一下这此标志位的含义:
●操作结果等于0时,零标志位置 1。
●操作使得目标操作数的最高位有进位时,进位标志位置1。
●符号标志位是目标操作数高位的副本,如果标志位置 1,表示是负数;标志位清 0,表示是正数。(假设0为正。)
●指令产生的结果超出了有符号目的操作数范围时,溢出标志位置1。
●指令使得目标操作数低字节中有偶数个1时,奇偶标志位置 1。
6.2.2 AND指令
AND 指令在两个操作数的对应位之间进行(按位)逻辑与(AND)操作,并将结果存放在目标操作数中:
AND destination, source
下列是被允许的操作数组合,但是立即操作数不能超过32位:
AND reg, reg
AND reg, mem
AND reg,imm
AND mem, reg
AND mem, imm
操作数可以是8 位、16 位、32 位和 64 位,但是两个操作数必须是同样大小。两个操作数的每一对对应位都遵循如下操作原则:如果两个位都是1,则结果位等于1;否则结果位等于 0。下表是出自第1章的真值表,有两个输入位x和 y。表的第三列是表达式x^y的值:
X | Y | X^Y |
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
AND指令可以清除一个操作数中的1个位或多个位,同时又不影响其他位。这个技术就称为位屏蔽,就像在粉刷房子时,用遮盖胶带把不用粉刷的地方(如窗户)盖起来。例如,假设要将一个控制字节从AL寄存器复制到硬件设备。并且当控制字节的位0和位3等于 0时,该设备复位。那么,如果想要在不修改 AL 其他位的条件下,复位设备,可以用下面的指令:
and AL, 11110110b ;清除位0和位3,其他位不变
如,设 AL 初始化为二进制数10101110,将其与1111 0110 进行AND 操作后,AL 等于1010 0110:
mov al, 10101110b
and al, 11110110b ;AL 中的结果=1010 0110
标志位 AND指令总是清除溢出和进位标志位,并根据目标操作数的值来修改符号标志位、零标志位和奇偶标志位。比如,下面指令的结果存放在 EAX 寄存器,假设其值为 0。在这种情况下,零标志位就会置1:
and eax, 1Fh
将字符转换为大写
AND指令提供了一种简单的方法将字符从小写转换为大写。如果对比大写A和小写a的 ASCII 码,就会发现只有位 5不同:
0 1 1 0 0 0 0 1 = 61h {'a'}
0 1 0 0 0 0 0 1 = 41h {'A'}
其他的字母字符也是同样的关系。把任何一个字符与二进制数11011111 进行 AND,则除位 5 外的所有位都保持不变,而位 5清 0。下例中,数组中所有字符都转换为大写:
;6.2.2.asm AND指令 数组中所有字符都转换为大写.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.data
array BYTE 50 DUP(97).code
main PROCmov ecx, LENGTHOF arraymov esi, OFFSET array
L1:and BYTE PTR [esi], 11011111b ;清除位5inc esiloop L1INVOKE ExitProcess,0
main ENDP
END main
运行调试:
转换后:
6.2.3 OR 指令
OR 指令在两个操作数的对应位之间进行(按位)逻辑或(OR)操作,并将结果存放在目标操作数中:
OR destination, source
OR 指令操作数组合与AND指令相同:
OR reg, reg
OR reg, mem
OR reg,imm
OR mem, reg
OR mem, imm
操作数可以是8 位、16 位、32 位和 64 位,但是两个操作数必须是同样大小。对两个操作数的每一对对应位而言,只要有一个输入位是1,则输出位就是1。下面的真值表(出自第1章)展示了布尔运算X∨Y:
X | Y | X∨Y |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
当需要在不影响其他位的情况下,将操作数中的1个位或多个位置为1时,OR 指令就非常有用了。比如,计算机与伺服电机相连,通过将控制字节的位2 置1来启动电机。假设该控制字节存放在AL 寄存器中,每一个位都含有重要信息,那么,下面的指令就只设置了位2:
or AL, 00000100b ;位2置1,其他位不变
如果 AL初始化为二进制数11100011,把它与00000100进行OR操作,其结果等于1110 0111:
mov al.11100011b
or a1, 00000100b ;AL中的结果=11100111
标志位 OR指令总是清除进位和溢出标志位,并根据目标操作数的值来修改符号标志位、零标志位和奇偶标志位。比如,可以将一个数与它自身(或0)进行OR运算,来获取该数值的某些信息:
or al, al
下表给出了零标志位和符号标志位对AL内容的说明:
零标志位 | 符号标志位 | AL 中的值 |
清0 | 清0 | 大于0 |
置1 | 清0 | 等于0 |
清0 | 置1 | 小于0 |
6.2.4 位映射集
有些应用控制的对象是从一个有限全集中选出来的一组项目。就像公司里的雇员,或者气象监测站的环境读数。在这些情景中,二进制位可以代表集合成员。与 Java HashSet 用指针或引用指向容器内对象不同,应用可以用位向量(或位映射)把一个二进制数中的位映射为数组中的对象。
如下例所示,二进制数的位从左边0号开始,到右边31号为止,该数表示了数组元素0、1、2 和 31 是名为 SetX 的集合成员:
SetX=1000 0000 0000 0000 0000 0000 0000 0111
(为了提供可读性,字节已经分开。)通过在特定位置与1进行 AND 运算,就可以方便地检测出该位是否为集合成员:
mov eax, SetX
and eax, 10000b ;元素[4]是Setx的成员吗?
如果本例中的 AND指令清除了零标志位,那么就可以知道元素[4]是 SetX的成员。
1.补集
补集可以用 NOT 指令生成,NOT 指令将所有位都取反。因此,可以用下面的指令生成上例中 SetX 的补集,并存放在EAX中:
mov eax, SetX
not eax ;SetX 的补集
2.交集
AND指令可以生成位向量来表示两个集合的交集。下面的代码生成集合SetX和SetY的交集,并将其存放在 EAX 中:
mov eax,SetX
and eax,Sety
SetX和SetY交集生成过程如下所示:
很难想象还有更快捷的方法生成交集。对于更大的集合来说,它所需要的位超过了单个寄存器的容量,因此,需要用循环来实现所有位的AND 运算。
3.并集
OR 指令生成位图表示两个集合的并集。下面的代码产生集合 SetX 和 SetY 的并集,并将其存放在EAX 中:
mov eax, Set
xor eax, Sety
OR指令生成SetX和Sety并集的过程如下所示:
6.2.5 XOR指令
XOR 指令在两个操作数的对应位之间进行(按位)逻辑异或(XOR)操作,并将结果存放在目标操作数中:
XOR destination, source
XOR 指令操作数组合和大小与 AND 指令及 OR 指令相同。两个操作数的每一对对应位都应用如下操作原则:如果两个位的值相同(同为 0 或同为1),则结果位等于0;否则结果位等于1。下表描述的是布尔运算x⊕y:
X | Y | X⊕Y |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
与0异或值保持不变,与1异或则被触发(求补)。对相同操作数进行两次 XOR 运算,则结果逆转为其本身。如下表所示,位x与位y进行了两次异或,结果逆转为x的初始值:
X | Y | X⊕Y | (X⊕Y)⊕Y |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 |
在 6.3.4 节中会发现,异或运算这种“可逆的”属性使其成为简单对称加密的理想工具。
标志位 XOR指令总是清除溢出和进位标志位,并根据目标操作数的值来修改符号标志位、零标志位和奇偶标志位。
检查奇偶标志 奇偶检查是在一个二进制数上实现的功能,计算该数中1的个数;如果计算结果为偶数,则说该数是偶校验;如果结果为奇数,则该数为奇校验。x86 处理器中,当按位操作或算术操作的目标操作数最低字节为偶校验时,奇偶标志位置1。反之,如果操作数为奇校验,则奇偶标志位清 0。一个既能检查数的奇偶性,又不会修改其数值的有效方法是,将该数与0进行异或运算:
mov al, 10110101b ;5个 1,奇校验
xor al, 0 ;奇偶标志位清0(奇)
mov al, 11001100b ;4 个 1,偶校验
xor al, 0 ;奇偶标志位置1(偶)
Visual Studio 用PE=1 表示偶校验,PE=0 表示奇校验。
16 位奇偶性 对16位整数来说,可以通过将其高字节和低字节进行异或运算来检测数的奇偶性:
mov ax, 64C1h ;0110 0100 1100 0001
xor ah, al ;奇偶标志位置1(偶)
将每个寄存器中的置1位(等于1的位)想象为一个8 位集合中的成员。XOR 指令把两个集合交集中的成员清 0,并形成了其余位的并集。这个并集的奇偶性与整个 16 位整数的奇偶性相同。
那么32位数值呢?如果将数值的字节进行编号,从B0到B3那么计算奇偶性的表达式为:B0 XOR B1 XOR B2 XOR B3
6.2.6 NOT 指令
NOT 指令触发(翻转)操作数中的所有位。其结果被称为反码。该指令允许的操作数类型如下所示:
NOT reg
NOT mem
例如,F0h 的反码是 0Fh:
mov al, 11110000b
not al ;AL=00001111b
标志位 NOT指令不影响标志位。
6.2.7 TEST指令
TEST 指令在两个操作数的对应位之间进行AND操作,并根据运算结果设置符号标志位、零标志位和奇偶标志位。TEST 指令与AND 指令唯一不同的地方是,TEST 指令不修改目标操作数。TEST 指令允许的操作数组合与 AND 指令相同。在发现操作数中单个位是否置位时,TEST 指令非常有用。
示例: 多位测试 TEST指令同时能够检查几个位。假设想要知道 AL 寄存器的位0和位3 是否置1,可以使用如下指令:
test al, 00001001b ;测试位0 和位 3
(本例中的 0000 1001 称为位掩码。)从下面的数据集例子中,可以推断只有当所有测试位都清 0时,零标志位才置1:
0 0 1 0 0 1 0 1 <- 输入值
0 0 0 0 1 0 0 1 <- 测试值
0 0 0 0 0 0 0 1 <- 结果: ZF=0
0 0 1 0 0 1 0 0 <- 输入值
0 0 0 0 1 0 0 1 <- 测试值
0 0 0 0 0 0 0 0 <- 结果: ZF=1
标志位 TEST指令总是清除溢出和进位标志位,其修改符号标志位、零标志位和奇偶标志位的方法与 AND指令相同。
6.2.8 CMP指令
了解了所有按位操作指令后,现在来讨论逻辑(布尔)表达式中的指令。最常见的布尔表达式涉及一些比较操作,下面的伪码片段展示了这种情况:
if A > B...
while x > 0 and x < 200 ...
if check forerror(N)=true
x86 汇编语言用 CMP指令比较整数。字符代码也是整数,因此可以用 CMP 指令。浮点数需要特殊的比较指令,相关内容将在第 12 章介绍。
CMP(比较)指令执行从目的操作数中减去源操作数的隐含减法操作,并且不修改任何操作数:
CMP destination, source
标志位 当实际的减法发生时,CMP指令按照计算结果修改溢出、符号、零、进位、辅助进位和奇偶标志位。如果比较的是两个无符号数,则零标志位和进位标志位表示的两个操作数之间的关系如右表所示:
如果比较的是两个有符号数,则符号标志位、零标志位和溢出标志位表示的两个操作数之间的关系如下表所示:
CMP指令是创建条件逻辑结构的重要工具。当在条件跳转指令中使用MP时,汇编语言的执行结果就和语句一样。
示例 下面用三段代码来说明标志位是如何受到CMP影响的。设AX=5,并与10进行比较,则进位标志位将置 1,原因是(5-10)需要借位:
mov ax, 5
cmp ax, 10 ;ZF = 0 and CF = 1
1000与1000比较会将零标志位置1,因为目标操作数减去源操作数等于0:
mov ax, 1000
mov cx, 1000
cmp cx, ax ;ZF = 1 and CF = 0
105与0进行比较会清除零和进位标志位,因为(105-0)的结果是一个非零的正整数。
mov si, 105
cmp si, 0 ;ZF = 0 and CF = 0
6.2.9置位和清除单个CPU标志位
怎样能方便地置位和清除零标志位、符号标志位、进位标志位和溢出标志位?有几种方法,其中的一些需要修改目标操作数。要将零标志位置1,就把操作数与0进行TEST或AND操作;要将零标志位清零,就把操作数与1进行 OR 操作:
test al, 0 ;零标志位置1
and al, 0 ;零标志位置1
or al, 1 ;零标志位清零
TEST指令不修改目的操作数,而AND指令则会修改目的操作数。若要符号标志位置1,将操作数的最高位和1进行OR操作;若要清除符号标志位,则将操作数最高位和0进行 AND 操作:
or al, 80h ;符号标志位置1
and al, 7Fh ;符号标志位清零
若要进位标志位置1,用STC指令;清除进位标志位,用CLC指令:
stc ;进位标志位置1
clc ;进位标志位清零
若要溢出标志位置1,就把两个正数相加使之产生负的和数;若要清除溢出标志位,则将操作数和0进行 OR操作:
mov al, 7Fh ;AL= +127
inc al ;AL=80h(-128),OF=1
or eax, 0 ;溢出标志位清零
6.2.10 64位模式下的布尔指令
大多数情况下,64 位模式中的64 位指令与32位模式中的操作是一样的。比如,如果源操作数是常数,长度小于32 位,而目的操作数是一个64 位寄存器或内存操作数,那么,目的操作数中所有的位都会受到影响:
.data
allones QWORD 0FFFFFFFFFFFFFFFFh
.code
mov rax, allones ;RAX = 0FFFFFFFFFFFFFFFFh
and rax, 80h ;RAX = 0000000000000080h
mov rax, allones ;RAX = 0FFFFFFFFFFFFFFFFh
and rax, 8080h ;RAX = 0000000000008080h
mov rax, allones ;RAX = 0FFFFFFFFFFFFFFFFh
and rax, 808080h ;RAX = 0000000000808080h
但是,如果源操作数是 32 位常数或寄存器,那么目的操作数中,只有低32 位会受到影响。如下例所示,只有RAX 的低 32 位被修改了:
mov rax,allones ;RAX = FFFFFFFFFFFFFFFF
and rax,80808080h ;RAX = FFFFFFFF80808080
当目的操作数是内存操作数时,得到的结果是一样的。显然,32位操作数是一个特殊的情况,需要与其他大小操作数的情况分开考虑。
6.2.11 本节回顾
1.编写一条指令,用 16 位操作数清除 AX 的高 8 位,而 AX 的低 8位不变。
答:and ax, 00FFh
2.编写一条指令,用16位操作数使AX的高8位置1,而AX的低8位不变。
答:or ax, 0FF00h
3.编写一条指令(不使用NOT),使EAX 中所有位取反。
答:xor eax, 0FFFFFFFFh
4.编写指令实现如下功能,当EAX 的32 位值为偶数时,将零标志位置1;当 EAX 的值为奇数时,将零标志位清零。
答:test eax, 1 ;若eax为奇数则低位置1
5.编写一条指令,将 AL 中的大写字母转换为小写字母;如果AL 中已包含小写字母,则不修改 AL。
答:or AL,00100000b
6.3 条件跳转
6.3.1 条件结构
x86 指令集中没有明确的高级逻辑结构,但是可以通过比较和跳转的组合来实现它们。执行一个条件语句需要两个步骤:第一步,用CMP、AND 或SUB 操作来修改CPU 状态标志位;第二步,用条件跳转指令来测试标志位,并产生一个到新地址的分支。下面是一些例子。
示例1 本例中的CMP指令把EAX的值与0进行比较,如果该指令将零标志位置1,则JZ(为零跳转)指令就跳转到标号L1:
cmp eax, 0
jz L1 ;如果ZF=1则跳转
L1:
示例2 本例中的AND指令对DL寄存器进行按位与操作,并影响零标志位。如果零标志位清零,则JNZ(非零跳转)指令跳转:
and dl, 10110000b
jnz L2 ;如果ZF=0则跳转
L2:
6.3.2 Jcond指令
当状态标志条件为真时,条件跳转指令就分支到目标标号。否则,当标志位条件为假时,立即执行条件跳转后面的指令。语法如下所示:
Jcond destination
cond 是指确定一个或多个标志位状态的标志位条件。下面是基于进位和零标志位的例子:
CPU 状态标志位最常见的设置方法是通过算术运算、比较和布尔运算指令。条件跳转指令评估标志位状态,利用它们来决定是否发生跳转。
用CMP指令 假设当EAX=5时,跳转到标号L1。在下面的例子中,如果EAX=5,CMP指令就将零标志位置1;之后,由于零标志位为1,JE指令就跳转到L1:
cmp eax, 5
je L1 ;如果相等则跳转
(JE指令总是按照零标志位的值进行跳转。)如果EAX 不等于 5,CMP就会清除零标志位,那么,JE 指令将不跳转。
下例中,由于 AX 小于 6,所以JL 指令跳转到标号 L1:
mov ax, 5
cmp ax, 6
jl L1 ;小于则跳转
例中,由于 AX 大于 4,所以发生跳转:
mov ax, 5
cmp ax, 4
jg L1 ;大于则跳转
完整代码测试笔记
;6.3.2.asm Jcond指令 当状态标志条件为真时,条件跳转指令就分支到目标标号。
;语法所示: Jcond destinationINCLUDE Irvine32.inc.data
L1str BYTE "leftOp == rightOp",0
L2str BYTE "leftOp < rightOp",0
L3str BYTE "leftOp > rightOp",0.code
main PROCmov eax, 5cmp eax, 5je L1 ;如果相等则跳转
next1:mov ax, 5cmp ax, 6jl L2 ;小于则跳转
next2:mov ax, 5cmp ax, 4jg L3 ;大于则跳转jmp quit
L1:mov edx, OFFSET L1str ;显示跳转提示call WriteStringcall Crlf jmp next1
L2:mov edx, OFFSET L2str ;显示跳转提示call WriteStringcall Crlf jmp next2
L3:mov edx, OFFSET L1str ;显示跳转提示call WriteStringcall Crlf ;换行
quit:nopINVOKE ExitProcess,0
main ENDP
END main
运行调试:
6.3.3 条件跳转指令类型
x86 指令集包含大量的条件跳转指令。它们能比较有符号和无符号整数,并根据单个CPU 标志位的值来执行操作。条件跳转指令可以分为四个类型:
●基于特定标志位的值跳转。
●基于两数是否相等,或是否等于(E)CX的值跳转
●基于无符号操作数的比较跳转
●基于有符号操作数的比较跳转
表 6-2展示了基于零标志位、进位标志位、溢出标志位、奇偶标志位和符号标志位的跳转。
1.相等性的比较
表6-3列出了基于相等性评估的跳转指令。有些情况下,进行比较的是两个操作数;其他情况下,则是基于CX、ECX或RCX的值进行跳转。表中符号1efOp和rightOp分别指的是CMP指令中的左(目的)操作数和右(源)操作数:
CMP leftOp, rightOp
操作数名字反映了代数中关系运算符的操作数顺序。比如,表达式X<Y中,X被称为1eftOp, Y 被称为 rightOp。
尽管正指令相当于JZ(为零跳转),INE指令相当于INZ(非零跳转),但是,最好是选择最能表明编程意图的助记符(JE或JZ),以便说明是比较两个操作数还是检查特定的状态标志位。
下述示例使用了JE、INE、JCXZ和JECXZ指令。仔细阅读注释,以保证理解为什么条件跳转得以实现(或不实现)。
;6.3.3_1.asm 相等性的比较INCLUDE Irvine32.inc.data
L1str BYTE "leftOp == rightOp",0
L2str BYTE "cx == 0",0
L5str BYTE "leftOp != rightOp",0.code
main PROC;示例 1:mov edx, 0A523hcmp edx, 0A523hjne L5 ;不发生跳转je L1 ;相等跳转;示例 2:mov bx, 1234hsub bx, 1234hjne L5 ;不发生跳转je L1 ;结果等0,即标志位ZF=1, 跳转;示例 3:mov cx, 0FFFFhinc cx ;cx = cx+1jcxz L2 ;cx==0就跳转;示例 4:xor ecx, ecxjcxz L2 ;cx==0就跳转L1: mov edx, OFFSET L1strcall WriteStringjmp quit
L2:mov edx, OFFSET L2strcall WriteStringjmp quit
L5:mov edx, OFFSET L5strcall WriteStringjmp quit
quit:call CrlfINVOKE ExitProcess,0
main ENDP
END main
运行调试:
示例 1: 示例 2:
示例 3: 示例 4:
2.无符号数比较
基于无符号数比较的跳转如表6-4所示。操作数的名称反映了表达式中操作数的顺序(比如1eftOp<rightOp)。表6-4中的跳转仅在比较无符号数值时才有意义。有符号操作数使用不同的跳转指令。
对下面的代码示例,阅读注释,以保证理解为什么跳转得以实现(或不实现):
示例 1
mov edx,-1
cmp edx, 0
jnl L5 ;不发生跳转(-1>0为假)
jnle L5 ;不发生跳转(-1>0为假)
jl L1 ;跳转(-1<0为真)
示例 2
mov bx, +32
cmp bx, -35
jng L5 ;不发生跳转(+32 <= -35为假)
jnge L5 ;不发生跳转(+32 < -35为假)
jge L1 ;跳转(+32 >= -35为真)
示例 3
mov ecx, 0
cmp ecx, 0
jg L5 ;不发生跳转(0>0为假)
jnl L1 ;跳转(0>=0为真)
示例 4
mov ecx, 0
cmp ecx, 0
jl L5 ;不发生跳转(0<0为假)
jng L1 ;跳转(0<=0为真)
6.3.4 条件跳转应用
测试状态位 汇编语言做得最好的事情之一就是位测试。通常,不希望改变进行位测试的数值,但是却希望能修改CPU状态标志位的值。条件跳转指令常常用这些状态标志位来决定是否将控制转向代码标号。例如,假设有一个名为status的8位内存操作数,它包含了与计算机连接的一个外设的状态信息。如果该操作数的位5等于1,表示外设离线,则下面的指令就跳转到标号:
mov al, status
test a1, 00100000b ;测试位5
jnz Deviceoffline
如果位0、1或4中任一位置1,则下面的语句跳转到标号:
mov al, status
test al, 00010011b ;测试位0、1、4
jnz InputDataByte
如果是位 2、3和7都置1使得跳转发生,则还需要AND和CMP指令:
mov al, status
and a1, 10001100b ;屏蔽位2、3和 7
cmp a110001100b ;所有位都置1?
je ResetMachine ;是:跳转
两个数中的较大数 下面的代码比较了EAX 和EBX 中的两个无符号整数,并且把其中较大的数送人EDX:
;6.3.4_1.asm 6.3.4 条件跳转应用 两个数中的较大数
;下面的代码比较了EAX 和EBX 中的两个无符号整数,并且把其中较大的数送人EDX:INCLUDE Irvine32.inc.data
L1str BYTE "The maximum value is: ",0.code
main PROCmov eax, 55mov ebx, 33mov edx, eax ;假设 EAX 存放较大的数cmp eax, ebx ;若EAX≥EBXjae L1 ;跳转到 L1mov edx, ebx ;否则,将 EBX 的值送入 EDX
L1: ;EDX 中存放的是较大的数mov eax, edxmov edx, offset L1strcall WriteStringcall WriteIntmov edx, eax
quit:call CrlfINVOKE ExitProcess,0
main ENDP
END main
运行调试:
三个数中的最小数 下面的代码比较了分别存放于三个变量V1、V2和 V3 的无符号 16位数值,并且把其中最小的数送人 AX:
;6.3.4_2.asm 6.3.4 条件跳转应用 三个数中的最小数
;下面的代码比较了分别存放于三个变量V1、V2和 V3 的无符号 16位数值,并且把其中最小的数送人 AX: INCLUDE Irvine32.inc.data
L2str BYTE "The minimum value is: ",0
V1 WORD 66
V2 WORD 55
V3 WORD 77.code
main PROCmov ax, V1 ;假设 V1是最小值cmp ax, V2 ;如果AX≤ V2jbe L1 ;跳转到 L1mov ax, V2 ;否则,将 V2 送入AX
L1: cmp ax, V3 ;如果 AX≤V3jbe L2 ;跳转到L2mov ax, V3 ;否则,将V3送入AXL2: and eax, 0000FFFFhmov edx, offset L2strcall WriteStringcall WriteIntquit:call CrlfINVOKE ExitProcess,0
main ENDP
END main
运行调试:
循环直到按下按键 下面的32位代码会持续循环,直到用户按下任意一个标准的字母数字键。如果输入缓冲区中当前没有按键,那么Irvine32库中的ReadKey函数就会将零标志位置1:
;6.3.4_3.asm 6.3.4 条件跳转应用
;环直到按下按键 下面的32位代码会持续循环,直到用户按下任意一个标准的字母数字键。
;如果输入缓冲区中当前没有按键,那么Irvine32库中的ReadKey函数就会将零标志位置1:INCLUDE Irvine32.inc.data
char BYTE ?
KeyStr BYTE "The pressed key is: ",0.code
main PROC
L1: mov eax, 10call Delaycall ReadKeyjz L1mov char, ALmov edx, offset KeyStrcall WriteString ;显示字符串call WriteChar ;显示ALcall CrlfINVOKE ExitProcess,0
main ENDP
END main
运行调试:
上述代码在循环中插人了一个10毫秒的延迟,以便MS-Windows有时间处理事件消息。如果省略这个延迟,那么按键可能被忽略。