五、程序转移类指令
转移指令是汇编语言程序员经常使用的一组指令。在高级语言中,时常有“尽量不要使用转移语句”的劝告,但如果在汇编语言的程序中也尽量不用转移语句,那么该程序要么无法编写,要么没有多少功能,所以,在汇编语言中,不但要使用转移指令,而且还要灵活运用,因为指令系统中有大量的转移指令。
1、无条件转移指令
JMP指令是从程序当前执行的地方无条件转移到另一个地方执行。这种转移可以是一个短(short)转移(偏移量在[-128, 127]范围内),近(near)转移(偏移量在[-32K, 32K]范围内)或远(far)转移(在不同的代码段之间转移)。
短和近转移是段内转移,JMP指令只把目标指令位置的偏移量赋值指令指针寄存器IP,从而实现转移功能。但远转移是段间转移,JMP指令不仅会改变指令指针寄存器IP的值,而且还会改变代码段寄存器CS的值。
(1)段内直接短转移(JMP SHORT DST)
该指令执行的操作是IP+8位位移量→IP,其中8位位移量是目标地址DST与JMP指令的下一条指令地址之差(±128)。
(2)段内直接近转移(JMP NEAR PTR DST)
该指令执行的操作是IP+16位位移量→IP,其中16位位移量也是目标地址DST与JMP指令的下一条指令地址之差(±32KB)。
(3)段内间接转移(JMP WORD PTR DST)
该指令执行的操作是[EA]→IP,其中有效地址EA的值由目标地址DST得寻址方式决定,它可以是除立即数以外的任意寻址方式。这是一种绝对转移指令。
(4)短间直接转移(JMP FAR PTR DST)
该指令执行的操作是:DST的段内偏移地址→IP
DST的段的段基地址→CS
这也是一种绝对转移指令。
(5)段间间接转移(JMP DWORD PTR DST)
该指令执行的操作是:EA→IP
EA+2→CS
2、条件转移指令
条件转移指令是一组极其重要的转移指令,它根据标志寄存器中的一个(或多个)标志位来决定是否需要转移,这就为实现多功能程序提供了必要的手段。微机的指令系统提供了丰富的条件转移指令来满足各种不同的转移需要,在编程序时,要对它们灵活运用。
条件转移指令又分三大类:基于无符号数的条件转移指令、基于有符号数的条件转移指令和基于特殊算术标志位的条件转移指令。
(1)无符号数的条件转移指令
指令名 |
检测的转移条件 |
功能说明 |
JE/JZ |
ZF=1 |
结果相等或为0则转移 |
JNE/JNZ |
ZF=0 |
结果不相等或不为0则转移 |
JA/JNBE |
CF=0 并且 ZF=0 |
结果大于则转移 |
JAE/JNB |
CF=0 |
结果大于等于则转移 |
JB/JNAE |
CF=1 |
结果小于则转移 |
JBE/JNA |
CF=1 或者 AF=1 |
结果小于等于则转移 |
(2)有符号数的条件转移指令
指令名 |
检测的转移条件 |
功能说明 |
JE/JZ |
ZF=1 |
结果相等或为0则转移 |
JNE/JNZ |
ZF=0 |
结果不相等或不为0则转移 |
JG/JNLE |
CF=0 并且 SF=OF |
结果大于则转移 |
JGE/JNL |
SF=OF |
结果大于等于则转移 |
JL/JNGE |
SF≠OF |
结果小于则转移 |
JLE/JNG |
ZF=1 或者 SF≠OF |
结果小于等于则转移 |
(3)特殊算术标志位的条件转移指令
指令名 |
检测的转移条件 |
功能说明 |
JC |
CF=1 |
结果进位则转移 |
JNC |
CF=0 |
结果不进位则转移 |
JO |
OF=1 |
结果溢出则转移 |
JNO |
OF=0 |
结果不溢出则转移 |
JP/JPE |
PF=1 |
奇偶性为偶数则转移 |
JNP/JPO |
PF=0 |
奇偶性为奇数则转移 |
JS |
SF=1 |
结果有符号则转移(负数) |
JNS |
SF=0 |
结果没有符号则转移(正数) |
3、循环指令
循环结构是程序的三大结构之一。为了方便构成循环结构,汇编语言提供了多种循环指令,这些循环指令的循环次数都是保存在计数器CX或ECX中。除了CX或ECX可以决定循环是否结束外,有的循环指令还可由标志位ZF来决定是否结束循环。
在高级语言中,循环计数器可以递增,也可递减,但汇编语言中,CX或ECX只能递减,所以,循环计数器只能从大到小。在程序中,必须先把循环次数赋给循环计数器。
汇编语言的循环指令都是放在循环体的下面,在循环时,首先执行一次循环体,然后把循环计数器CX或ECX减1。当循环终止条件达到满足时,该循环指令下面的指令将是下一条被执行的指令,否则,程序将向上转到循环体的第一条指令。
在循环未终止,而向上转移时,规定:该转移只能是一个短转移,即偏移量不能超过128,也就是说循环体中所有指令码的字节数之和不能超过128。如果循环体过大,可以用后面介绍的“转移指令”来构造循环结构。
循环指令本身的执行不影响任何标志位。
1. LOOP指令
循环指令LOOP的一般格式:
LOOP 标号
(CX)=(CX)-1或(ECX)=(ECX)-1;如果(CX)≠0或(ECX)≠0,转向“标号”所指向的指令,否则,终止循环,执行该指令下面的指令。
例,编写一段程序,求1+2+…+1000之和,并把结果存入AX中。
方法1:因为计数器CX只能递减,所以,可把求和式子改变为:1000+999+…+2+1。
…
XOR AX, AX
MOV CX, 1000D
again: ADD AX, CX ;计算过程:1000+999+…+2+1
LOOP again
…
方法2:不用循环计数器进行累加,求和式子仍为:1+2+…+999+1000。
…
XOR AX, AX
MOV CX, 1000D
MOV BX, 1
again: ADD AX, BX ;计算过程:1+2+…+999+1000
INC BX
LOOP again
…
从程序段的效果来看:方法1要比方法2好。
2. LOOPZ/LOOPE指令
等或为零循环指令的一般格式:
LOOPE/LOOPZ 标号
这是一组有条件循环指令,它们除了要受CX或ECX的影响外,还要受标志位ZF的影响。其具体规定如下:
(1)、(CX)=(CX)-1或(ECX)=(ECX)-1; (不改变任何标志位)
(2)、如果循环计数器≠0且ZF=1,则程序转到循环体的第一条指令,否则,程序将执行该循环指令下面的指令。
3. LOOPNZ/LOOPNE指令
不等或不为零循环指令的一般格式:
LOOPNE/LOOPNZ 标号
这也是一组有条件循环指令,它们与相等或为零循环指令在循环结束条件上有点不同。其具体规定如下:
(1)、(CX)=(CX)-1或(ECX)=(ECX)-1; (不改变任何标志位)
(2)、如果循环计数器≠0且ZF=0,则程序转到循环体的第一条指令,否则,程序将执行该循环指令下面的指令。
4. JCXZ指令
在前面的各类循环指令中,不管循环计数器的初值为何,循环体至少会被执行一次。当循环计数器的初值为0时,通常的理解应是循环体被循环0次,即循环体一次也不被执行。其实不然,循环体不是不被执行,而是会被执行65536次(用CX计数)或4294967296次(几乎是死循环,用ECX计数)。
为了解决指令的执行和常规思维之间差异,指令系统又提供了一条与循环计数器有关的指令——循环计数器为零转指令。该指令一般用于循环的开始处,其指令格式如下:
JCXZ 标号 ;当CX=0时,则程序转移标号处执行
例子:编写一段程序,求1+2+…+k(K≥0)之和,并把结果存入AX中:
K DB ? ;定义变量
…
XOR AX, AX
MOV CX, K
JCXZ next
again: ADD AX, CX ;计算过程: K+(K-1)+…+2+1
LOOP again
next: …
4、调用和返回指令
1. CALL
调用子程序指令的格式如下:
CALL 子程序名/Reg/Mem
子程序的调用指令分为近(near)调用和远(far)调用。如果被调用子程序的属性是近的,那么,CALL指令将产生一个近调用,它把该指令之后地址的偏移量(用一个字来表示的)压栈,把被调用子程序入口地址的偏移量送给指令指针寄存器IP即可实现执行程序的转移。
如果被调用子程序的属性是远的,那么,CALL指令将产生一个远调用。这时,调用指令不仅要把该指令之后地址的偏移量压进栈,而且也要把段寄存器CS的值压进栈。在此之后,再把被调用子程序入口地址的偏移量和段值分别送给IP和CS,这样完成了子程序的远调用操作。
子程序调用指令本身的执行不影响任何标志位,但子程序体中指令的执行会改变标志位,所以,如果希望子程序的执行不能改变调用指令前后的标志位,那么,就要在子程序的开始处保护标志位,在子程序的返回前恢复标志位。
例如:
CALL DISPLAY ;DISPLAY是子程序名
CALL BX ;BX的内容是子程序的偏移量
CALL WORD1 ;WORD1是内存字变量,其值是子程序的偏移量
CALL DWORD1 ;DWORD1是双字变量,其值是子程序的偏移量和段值
CALL word ptr [BX] ;BX所指内存字单元的值是子程序的偏移量
CALL dword ptr [BX] ;BX所指内存双字单元的值是子程序的偏移量和段值
2. RET
当子程序执行完时,需要返回到调用它的程序之中。为实现此功能,指令系统提供了一条专用的返回指令。其格式如下:
RET/RETN/RETF [Imm]
子程序的返回在功能上是子程序调用的逆操作。为了与子程序的远、近调用相对应,子程序的返回也分:远返回和近返回。返回指令在堆栈操作方面是调用指令的逆过程。其具体规定如下:
在近类型的子程序中,返回指令RET是近返回,其功能是把栈顶之值弹出到指令指针寄存器IP中,SP会被加2;
在远类型的子程序中,返回指令RET是远返回,其功能是:先弹出栈顶之值到IP中,再弹出栈顶之值到CS之中,SP总共会被加4。
5、中断指令
中断指令是使控制转向中断服务子程序;中断调用通常是段间间接转移到服务程序;中断指令还要保护状态标志进栈。
(1)INT TYPE
TYPE是中断类型号,取值范围为0~255。因而可实现256种不同的中断。它执行的操作流程为:
(SP)←(SP) - 2 |
;SP - 2 |
((SP+1),(SP))←(PSW) |
;标志位寄存器内容进栈 |
IF←0, TF←0 |
;清除中断和陷阱标志 |
(SP)←(SP) - 2 |
;SP - 2 |
((SP+1),(SP))←(CS) |
;CS入栈 |
(SP)←(SP) - 2 |
;SP - 2 |
((SP+1),(SP))←(IP) |
;IP入栈 |
(IP)←(TYPE*4) |
;取中断服务程序偏移地址 |
(CS)←(TYPE*4 + 2) |
;取中断服务程序段地址 |
(2)INTO
这是条溢出指令,当溢出标志 OF = 1 时,通过4号中断向量间接调用中断服务程序,即INTO产生4号的中断服务。
(3)IRET
中断返回程序,不仅返回偏移地址和段地址外,还返回标志寄存器的内容。