默认情况下,CPU加载程序并按顺序执行其中的指令。但是,当前正在执行的指令有可能是条件处理指令,这也就意味着当前指令有可能会根据CPU的状态标志值(零标志、符号标志、进位标志等)把控制权转移到程序中的一个新的地址处。汇编语言程序使用条件处理指令实现高级语言中的条件处理(IF)语句和循环语句。每种条件处理语句都有可能导致控制转移到(跳转到)内存中的一个新地址处。控制转移(transfer of control),或者说分支转移(branch),是一种改变语句执行顺序的方法。控制转移可分为两种:
1.无条件转移:无论在何种情况下,程序都转移到一个新的地址,指令指针中装入一个新值,CPU在新的地址继续执行。JMP指令就是一个很好的例子。
2.条件转移:如果特定条件满足则程序转移。Intel提供了大量的条件转移指令,这些指令结合起来可以创建各种条件逻辑结构。CPU根据ECX寄存器和标志寄存器的内容解释条件的真或假。
下面分别介绍JMP指令和LOOP指令:
JMP指令:
JMP指令向代码段的目的地址做无条件转移。标识目的地址的代码标号将由汇编翻译成偏移地址,JMP指令的格式是:JMP 目的地址
CPU执行无条件转移指令时,目标标号的偏移地址被装入指令指针中,CPU立即开始在新的地址继续执行指令。通常情况下,JMP指令只能跳转到当前过程内的标号处。
创建一个循环:JMP指令提供了一种创建循环的简单方法,只要跳到循环顶端的标号处就可以了:
top:
.
.
jmp top ;无限循环
JMP指令是无条件的,因此循环会永无休止地持续下去,直到满足其他条件退出为止。
LOOP指令:
LOOP指令重复执行一块语句,执行的次数是特定的,ECX被自动用做计数器,在每次循环之后减1,格式如下:
LOOP 目的地址
LOOP指令的执行包含两步:首先,ECX减1,接着与0相比较。如果ECX不等于0,则跳转到目的地址(标号)处;如果ECX等于0,则不发生跳转,这时控制权将转移到紧跟在LOOP指令后面的指令处。
在实地址模式下,用做默认循环计数器的是CX寄存器。不论是在实地址模式还是保护模式下,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,结果是循环将重复4294967296次!如果是用CX做循环计数器,循环将重复65535次。
循环的目的地址与当前地址只能在相距-128~+127字节的范围之内。机器指令的平均大小是2字节左右,因此一个循环平均最多只能包含大约42条指令。下面语句是由于LOOP指令的目的标号地址距离过远时MASM产生的错误信息:
error A2075:jump destination too far: by 14byte(s)
如果在循环内修改了ECX的值,LOOP指令就有可能无法正确工作了。下例中ECX在循环中加1,因此执行LOOP指令后ECX永远不会为0(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 ;重复外层循环
作为一条一般性的规则,应尽量避免使用嵌套深度超过两层的循环。否则,管理循环计数将使人头痛。如果算法要求多层循环嵌套的话,可以把一部分内层循环代码移到子程序中。
下面给出两个例子:
1.整数数组求和
刚开始编程时,大概没有比计算数组元素之和更常见的任务了。在汇编语言中,可按照以下步骤:
1)把数组的起始地址送入一个寄存器,这个寄存器将用在变址操作数中。
2)ECX设置为数组中元素的数目(16位模式下使用CX)。
3)把另外一个寄存器清零用于保存累加和。
4)创建一个标号标识循环的开始。
5)在循环体中,用间接寻址方式把数组的每个元素同用于存放累加和的寄存器相加。
6)变址寄存器指向下一个数组元素。
7)使用LOOP指令重复执行由开始标号标明的循环体。
前3步的顺序可以任意调整。
.data
intarray word 100h,200h,300h,400h
.code
main proc
mov edi,offset intarray ;intarray的地址
mov ecx,lengthof intarray;循环计数器
mov ax,0 ;累加器清零
L1:
add ax,[edi] ;加上一个整数
add edi,type intarray ;指向下一个整数
loop L1 ;重复循环直到ECX=0为止
exit
main endp
end main
2.复制字符串
程序经常要从一个地址向另一个地址复制大块的数据,数据通常是字符串,但也可能是任何其他类型的对象。通过下面的使用循环进行字符串复制的例子,让我们看看在汇编语言中如何实现类似的复制功能的。进行这种类型的操作很适合适合使用变址寻址,因为两个字符串都能用同一个变址寄存器来索引。目的串必须有足够的空间容纳复制过来的字符串,包括尾部的空字符:
.data
source byte "This is the source string",0
target byte sizeof source dup(0),0
.code
main proc
mov esi,0 ;变址寄存器
mov ecx,sizeof source ;循环计数器
L1:
mov al,source[esi];从源中取一个字符
mov target[esi],al;将该字符存储在目的中
inc esi;移到下一个字符
loop L1;重复复制整个字符串
exit
main endp
end main
mov指令不能同时对两个内存操作数进行操作,因此每个字符首先从源字符串送至AL,然后再从AL送到目的字符串中。
---------------完毕。欢迎交流。