默认情况下,CPU 是顺序加载并执行程序。但是,当前指令有可能是有条件的,也就是说,它按照 CPU 状态标志(零标志、符号标志、进位标志等)的值,把控制转向程序中的新位置。
汇编语言程序使用条件指令来实现如 IF 语句的高级语句与循环。每条条件指令都包含了一个可能的转向不同内存地址的转移(跳转)。控制转移,或分支,是一种改变语句执行顺序的方法,它有两种基本类型:
JMP 指令无条件跳转到目标地址,该地址用代码标号来标识,并被汇编器转换为偏移 量。语法如下所示:
JMP destination
当 CPU 执行一个无条件转移时,目标地址的偏移量被送入指令指针寄存器,从而导致迈从新地址开始继续执行。
JMP 指令提供了一种简单的方法来创建循环,即跳转到循环开始时的标号:
top:
.
.
jmp top ;不断地循环
JMP 是无条件的,因此循环会无休止地进行下去,除非找到其他方法退岀循环。
LOOP 指令,正式称为按照 ECX 计数器循环,将程序块重复特定次数。ECX 自动成为计数器,每循环一次计数值减 1。语法如下所示:
LOOP destination
循环目标必须距离当前地址计数器 -128 到 +127 字节范围内。LOOP 指令的执行有两个步骤:
如果 ECX 不等于 0,则跳转到由目标给岀的标号。否则,如果 ECX 等于 0,则不发生跳转,并将控制传递到循环后面的指令。
实地址模式中,CX 是 LOOP 指令的默认循环计数器。同时,LOOPD 指令使用 ECX 为循环计数器,LOOPW 指令使用 CX 为循环计数器。
下面的例子中,每次循环是将 AX 加 1。当循环结束时,AX=5, ECX=0:
mov ax,0
mov ecx,5
L1:
inc ax
loop L1
一个常见的编程错误是,在循环开始之前,无意间将 ECX 初始化为 0。如果执行了这个操作,LOOP 指令将 ECX 减 1 后,其值就为 FFFFFFFFh,那么循环次数就变成了 4 294 967 296!如果计数器是 CX (实地址模式下),那么循环次数就为 65 536。
有时,可能会创建一个太大的循环,以至于超过了 LOOP 指令允许的相对跳转范围。下面给出是 MASM 产生的一条错误信息,其原因就是 LOOP 指令的跳转目标太远了:
基本上,在一个循环中不用显式的修改 ECX,否则,LOOP 指令可能无法正常工作。下例中,每次循环 ECX 加 1。这样 ECX 的值永远不能到 0,因此循环也永远不会停止:
top:
.
.
inc ecx
loop top
如果需要在循环中修改 ECX,可以在循环开始时,将 ECX 的值保存在变量中,再在 LOOP 指令之前恢复被保存的计数值:
.data
count DWORD ?
.code
mov ecx, 100 ;设置循环计数值
top:
mov count, ecx ;保存计数值
.
mov ecx, 20 ;修改 ECX
.
mov ecx, count ;恢复计数值
loop top
当在一个循环中再创建一个循环时,就必须特别考虑外层循环的计数器 ECX,可以将它保存在一个变量中:
.data
count DWORD ?
.code
mov ecx, 100 ;设置外层循环计数值
L1:
mov count, ecx ;保存外层循环计数值
mov ecx, 20 ;设置内层循环计数值
L2 :
loop L2 ;重复内层循环
mov ecx, count ;恢复外层循环计数值
loop L1 ;重复外层循环
提示:作为一般规则,多于两重的循环嵌套难以编写。如果使用的算法需要多重循环,则将一些内层循环用子程序来实现。
在调试期间,如果想要显示数组的内容,步骤如下:选择 Debug 菜单 -> 选择 Windows -> 选择 Memory -> 选择Memory 1。则出现内存窗口,可以用鼠标拖动并停靠在 Visual Studio 工作区的任何一边。还可以右键点击该窗口的标题栏,表明要这个窗口浮动在编辑窗口之上。
在内存窗口上端的 Address 栏里, 键入 & 符号和数组名称,然后点击 Enter。比如,&myArray 就是一个有效的地址表达式。内存窗口将显示从这个数组地址开始的内存块,如下图所示。
如果数组的值是双字,可以在内存窗口中,点击右键并在弹出菜单里选择 4-byte integer。还有不同的格式可供选择,包括 Hexadecimal Display,Signed Display(有符号显示),和 Unsigned Display(无符号显示)。下图显示了所有的选项。
在刚开始编程时,几乎没有任务比计算数组元素总和更常见了。汇编语言实现数组求和步骤如下:
步骤 1 到步骤 3 可以按照任何顺序执行。下面的短程序实现对一个 16 位整数数组求和。
; 数组求和(SumArray. asm)
.386
.model flat,stdcall
.stack 4096
ExitProcess proto,dwExitCode:dword
.data
intarray DWORD 10000h,20000h,30000h,40000h
.code
main PROC
mov edi, OFFSET intarray ; 1: EDI=intarray 地址
mov ecx, LENGTHOF intarray ; 2 :循环计数器初始化
mov eax,0 ; 3: sum=0
L1: ; 4:标记循环开始的地方
add eax, [edi] ; 5:加一个整数
add edi, TYPE intarray ; 6:指向下一个元素
loop L1 ; 7:重复,直到 ECX=0
invoke ExitProcess, 0
main ENDP
END main
程序常常要将大块数据从一个位置复制到另一个位置。这些数据可能是数组或字符串,但是它们可以包括任何类型的对象。
现在看看在汇编语言中如何实现这种操作,用循环来复制一个字符串,而字符串表示为带有一个空终止值的字节数组。变址寻址很适合于这种操作,因为可以用同一个变址寄存器来引用两个字符串。目标字符串必须有足够的空间来接收 被复制的字符,包括最后的空字节:
;复制字符串 (CopyStr.asm)
.386
.model flat,stdcall
.stack 4096
ExitProcess proto,dwExitCode:dword
.data
source BYTE "This is the source string", 0
target BYTE SIZEOF source DUP(0)
.code
main PROC
mov esi, 0 ;变址寄存器
mov ecx, SIZEOF source ;循环计数器
L1: ;从源字符串获取一个字符
mov al, source [esi] ;保存到目标字符串
mov target [esi] , al ;指向下一个字符
inc esi ;重复,直到整个字符串完成
loop L1
invoke ExitProcess,0
main ENDP
END main
MOV 指令不能同时有两个内存操作数,所以,每个源字符串字符送入 AL,然后再从 AL 送入目标字符串。