微机原理与接口技术(基于32位机)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.3 80486微处理器的基本指令系统

80486微处理器的指令系统包含133条基本指令,按功能可分为如下六大类指令:

  • 数据传送类指令;
  • 算术运算类指令;
  • 逻辑运算与移位类指令;
  • 串操作类指令;
  • 程序控制类指令;
  • 处理器控制类指令。

由于80486微处理器指令系统的功能很强,同时也比较复杂,学习和掌握好这些指令会有一定的难度,因此,这部分内容的学习应主要掌握指令的格式、功能、操作及对标志位的影响。

3.3.1 数据传送类指令

数据传送类指令的功能是完成寄存器与寄存器之间、寄存器与存储器之间及寄存器与I/O端口之间的字节数据或字数据的传送。这类指令的共同特点是不影响标志寄存器的内容。数据传送类指令又可分为4种类型,见表3.1。

表3.1 数据传送类指令

注:d表示目标操作数,s表示源操作数。

1.通用数据传送指令

通用数据传送指令包括最基本的传送指令MOV、堆栈操作指令PUSH与POP、数据交换指令XCHG和字节翻译指令XLAT。

(1)传送指令

MOV(move):最基本的传送指令。

格式:MOV d,s

操作:(d)←(s),将由s指定的源操作数送目标操作数d。

注:( )表示有关的内容。

数据传送方向如图3.8所示。

图3.8 数据传送方向示意图

在这类传送指令中,由s与d分别指定源操作数与目标操作数。源操作数可以是8位或16位寄存器操作数,也可以是存储器中的某个字节/字数据,或者是8位/16位立即数。目标操作数不允许为立即数,其他同源操作数一样,且两者不能同时为存储器操作数。例如:

MOV AL,30H ; 寄存器←立即数(AL←30H),字节传送
MOV AX,1122H ; 寄存器←立即数(AX←1122H),字传送
MOV BH,CH ; 寄存器←寄存器,字节传送
MOV SI,BX ; 寄存器←寄存器,字传送
MOV CL,ADDR ; CL←内存单元ADDR中的字节内容
MOV AX,[DI] ; AH←(DI+1),AL←(DI),[DI]指定内存单元的字地址
MOV [SI] ,DX ; [SI+1]←DH,[SI]←DL,[SI]指定内存单元的字地址
MOV [2000H] ,DS ; [2001H]←DS15~8,[2000H]←DS7~0,[2000H]指定内存单元的字地址
MOV SS,AX ; 段寄存器←寄存器,字传送

还应注意,CS和IP这两个寄存器不能作为目标操作数,也就是说,这两个寄存器的值不能用MOV指令来修改。另外,当操作数采用BX、SI、DI进行间接寻址时,默认的段寄存器为DS,访问数据段。当采用BP进行间接寻址时,默认的段寄存器为SS,访问堆栈段。

例如:MOV AL,00H ; AL←00H

   MOV BL,01H ; BL←01H

   MOV CL,02H ; CL←02H

   MOV BL,AL ; BL←00H

   MOV CL,BL ; CL←00H

这是一段全部由字节传送类指令组成的程序。执行每条指令后的结果参见程序中的注释。程序执行后,AL=00H,BL=00H,CL=00H。该程序段的功能是将AL、BL和CL寄存器清0。

例如:MOV AX,5060H

   MOV [2100H],AX

执行这两条指令组成的程序段后,相应存储单元中的内容是什么?

第一条指令的执行结果是AX=5060H,第二条指令将AX中的内容传送到内存中偏移地址为2100H的字单元中,所以[2101H]=50H,[2100H]=60H。

(2)堆栈操作指令

80486微处理器对堆栈的操作遵循“先进后出”的原则,即最先存入堆栈的数据最后才能取出,最后存入的数据可最先取出。80486微处理器的堆栈采取“向下生长”的编址方式,即越靠近堆栈底部,其地址越大;越靠近堆栈顶部,其地址越小。在堆栈操作的过程中,堆栈指针SP的内容始终指向堆栈栈顶的地址(开始时,它指在堆栈的底部,地址最高)。在80486微处理器指令系统中,有两条专用于堆栈操作的指令PUSH和POP。除此之外,子程序调用及返回指令、中断调用及返回指令的操作过程,都会对堆栈有影响。下面讲述80486微处理器的压栈和出栈指令。

① 压栈指令PUSH(push word onto the stack):将数据压入堆栈。格式:PUSH s

操作:(SP-1),(SP-2)←(s)

    SP←SP-2

其中,s是源操作数,表示入栈的字操作数,除了不允许使用立即数外,寄存器、存储器、段寄存器(CS除外)都可以作为源操作数。

具体操作过程:先将SP减1,将操作数的高位字节送入当前SP所指单元中,然后再将SP减1,将操作数的低位字节又送入当前SP所指单元中。也就是说,每执行一次PUSH操作,将源操作数s指定的一个字数据压入堆栈中由SP指定的相邻两个单元保存起来,数据的高位字节压入高地址单元,低位字节压入低地址单元(即高位对应高地址,低位对应低地址),堆栈指针SP减2,SP总是指向最后压入数据的单元地址,即栈顶。

例如:PUSH AX

指令执行前SS=4000H,SP=2500H,AX=3125H,指令执行过程及堆栈操作如图3.9所示。

图3.9 PUSH AX指令执行过程及堆栈操作

指令执行时,首先SP减1,则SP=24FFH,将AX的高位字节AH=31H送入SP和SS所指定的424FFH单元中,然后将SP再减1,此时SP=24FEH,将AX的低位字节AL=25H送入SP和SS所指定的424FEH单元中。执行完PUSH指令,SP=24FEH,是在原来SP=2500H的基础上减2所得的结果。

② 出栈指令POP(pop word from the stack):将数据弹出堆栈。

格式:POP d

操作:(d)←(SP+1),(SP)

   SP←SP+2

其中,d为目标操作数,表示由堆栈弹出的字操作数所在的目标地址,除了立即数和CS段寄存器外,寄存器、存储器和段寄存器都可以作为目标操作数。

具体操作过程:先将当前SP所指的栈顶单元的内容弹出,并送入d指定的低位字节单元,SP内容加1指向下一个单元。然后再将当前SP所指栈顶单元中的内容弹出,并送入d指定的高位字节单元,SP的内容再加1。也就是说,每执行一次POP操作,由当前SP所指的栈顶字单元中弹出一个字数据,送入d指定的目标操作数,高位对应高地址,低位对应低地址,堆栈指针SP加2,SP总是指向下一个该弹出数据的单元地址,即栈顶。

例如:POP BX

指令执行前,SS=8000H,SP=2000H,堆栈区段偏移地址为2000H单元中的内容为0BH,2001H单元中的内容为0AH。指令执行过程及堆栈操作如图3.10所示。

指令执行时,首先将SP=2000H所指栈顶地址单元中的内容0BH弹出,并送入BX的低位字节BL中,然后SP加1为2001H,再将当前SP所指栈顶地址单元中的内容0AH弹出,送入BX的高位字节BH中,最后再将SP加1,指向2002H单元,SP在原来2000H的基础上增加2。指令执行后,BX=0A0BH,SP=2002H。

堆栈操作在计算机中常常被用来保护现场。如果在程序中要用到某些寄存器,但它们原来的内容在后面程序执行过程中还要用到,这时就可以使用PUSH指令,将这些寄存器的内容暂时保存在堆栈中,以后要用到这些内容时,再使用POP指令恢复。其程序形式如下:

图3.10 POP BX指令执行过程及堆栈操作

PUSH AX ; 将AX的内容压入堆栈保护
PUSH BX ; 将BX的内容压入堆栈保护
… ; 在此程序段中可使用AX,BX
POP BX ; 恢复BX原先的内容
POP AX ; 恢复AX原先的内容

此时,要遵循堆栈操作“先进后出”的原则,应特别注意有关内容的入栈及出栈的顺序,防止造成数据的交叉或混乱。

在实际使用时,还可利用堆栈操作将某些寄存器或存储单元内容进行交换。

例如:MOV SP,3000H ; 设置堆栈指针,SP←3000H

   MOV AX,1234H ; 设置AX初始值,AX←1234H

   MOV BX,5678H ; 设置BX初始值,BX←5678H

   PUSH AX ; 将AX内容压入堆栈

   PUSH BX ; 将BX内容压入堆栈

   POP AX ; AX←5678H

   POP BX ; BX←1234H

其执行过程如图3.11所示。

图3.11 程序执行情况

① 执行PUSH AX指令后,SP1=SP-2=2FFEH,将AX的内容1234H压入堆栈2FFFH和2FFEH单元中。

② 执行PUSH BX指令后,SP2=SP1-2=2FFCH,将BX的内容5678H压入堆栈2FFDH和2FFCH单元中。

③ 执行POP AX指令后,将栈顶SP2指定的2FFCH和2FFDH单元中原先BX的两个字节数据弹出,并送入目标操作数AX,SP3=SP2+2=2FFEH。

④ 执行POP BX指令后,将栈顶SP3所指定的2FFEH和2FFFH两个单元中保存的原AX内容弹出,并送入目标操作数BX,SP4=SP3+2=3000H。程序执行后,SP=SP4=3000H,AX=5678H,BX=1234H。

讨论:

① 堆栈指针SP的变化情况。由于每执行一条PUSH指令SP-2,而每执行一条POP指令SP+2,所以SP=3000H-2-2+2+2=3000H。

② AX、BX寄存器中的内容变化情况。当进行PUSH操作时,先压入AX中的内容,后压入BX中的内容。而当进行POP操作时,最先弹出的内容(原BX的内容)送入AX,最后弹出的内容(原AX的内容)送入BX。由于堆栈操作先进后出的原则,导致AX和BX中的内容发生了交换。

(3)XCHG(exchange):交换指令

格式:XCHG d,s

操作:(d)←→(s)

交换指令的功能:将s表示的源操作数的内容与d表示的目标操作数的内容相互交换,这两个操作数都可以是字节或字类型。交换可以在寄存器与寄存器之间、寄存器与存储单元之间进行,但不能在两个存储单元之间交换数据。段寄存器与指令指针IP也不能作为源操作数和目标操作数。

例如:XCHG AX,CX ; AX←→CX,将AX,CX中原先保存的内容相互交换

图3.12 数码显示代码表

(4)XLAT(table look-up translation):字节转换指令

格式:XLAT

操作:AL←(BX+AL)

字节转换指令XLAT用来将一种字节代码转换成另一种字节代码。它将BX中的内容(代码表格首地址)和AL中的内容(表格偏移量)相加作为有效地址,并读出地址单元中的内容传送到AL寄存器中。指令执行后,AL中的内容是所要转换的代码。

【例3.1】 将0~F的十六进制数转换为七段数码管的显示代码。

此代码位于当前数据段中,DS=2000H,起始单元的偏移地址值为0300H。若将十六进制数B转换成相应的七段码,步骤如下。

① 建立数码显示代码表,如图3.12所示。将表首单元的偏移地址送入BX,BX=0300H。

② 将待转换的十六进制数在表中的序号(又称索引值)0BH送入AL中。

③ 执行XLAT指令:

MOV BX,0300H ; BX←0300H
MOV AL,0BH ; AL←0BH
XLAT ; AL←03H

执行上述指令后,将物理地址=DS×16+偏移地址=2000H×16+0300H+0BH=2030BH单元中的内容03H传送给AL,从而完成代码转换过程,AL中的内容03H即为十六进制数B转换成的七段显示代码(共阳极数码管)。

2.目标地址传送指令

目标地址传送指令共有三条,可将操作数的段基址或偏移地址传送到指定的寄存器中。

(1)LEA(load effective address):取有效地址指令

格式:LEA d,s

操作:(d)←EA

该指令可将源操作数s的有效地址EA传送到指令指定的寄存器中。源操作数s只能是各种寻址方式的存储器操作数,而寄存器、立即数和段寄存器都不能作为源操作数。目标操作数d可以是一个16位的通用寄存器。这条指令常用来使一个寄存器作为地址指针。

例如:LEA BX,[SI]

指令执行前,SI=2500H,则EA=2500H;

指令执行后,BX=2500H。

该指令的执行结果是将源操作数确定的存储单元的有效地址2500H传送到目标操作数确定的寄存器BX中(关注的是存储单元的有效地址,而不是其中的内容)。要特别注意,指令LEA BX,[SI]与指令MOV BX,[SI]的区别。前者将SI中的内容2500H作为存储器的有效地址送入BX中,后者则将SI寄存器间接寻址方式确定的相邻两个存储单元中的内容送入BX中。若DS=5000H,该数据段中52500H字单元中的内容为1234H,这两条指令的操作过程如图3.13所示。

图3.13 LEA和MOV指令的执行过程

(2)LDS(load data segment register):取指针送寄存器和DS指令

格式:LDS d,s

操作:(d)←(s)

    DS←(s+2)

指令中的源操作数s确定一个双字类型的存储器操作数的首地址,目标操作数d指定一个16位的寄存器操作数(不允许使用段寄存器)。

LDS指令的功能:从源操作数s所指定的存储单元开始的连续4个存储单元中,取出某变量的地址指针,将其前两个字节(即偏移地址值)传送到目标操作数d所指定的16位通用寄存器中,而将后两个字节(即段基址值)传送到DS段寄存器中。

例如:LDS BX,LOP [DI]

若DS=4000H,DI=0200H,LOP=0010H,则该双字操作数存储单元的物理地址为:

物理地址=DS×16+DI+LOP=40000H+0200H+0010H=40210H

若指令执行前,BX=30A0H,双字操作数在数据段中的存放情况如图3.14(a)所示,则指令执行后,BX=2050H,DS=8000H,如图3.14(b)所示。

图3.14 LDS BX,LOP [DI]的执行情况

(3)LES(load extra segment register):取指针送寄存器和ES指令

格式:LES d,s

操作:(d)←(s)

   ES←(s+2)

LES指令的操作与LDS指令基本类似,所不同的是以ES代替DS,即将源操作数所指定的地址指针中的后两个字节(段基址)传送到ES段寄存器中,而不是DS段寄存器中。

例如:LES DI,[BX]

若DS=5000H,BX=2000H,则该双字操作数的物理地址为:

物理地址=DS×16+BX=50000H+2000H=52000H

若指令执行前,DI=1234H,ES=6400H,数据区段中(52000H)=FFH,(52001H)=20H,(52002H)=00H,(52003H)=81H,则指令执行后,DI=20FFH,ES=8100H。

3.标志位传送指令

标志位传送指令的操作涉及标志寄存器,利用这些指令,可以读出标志寄存器的内容,也可对标志寄存器的标志位进行设置。标志位传送指令共有4条,这些指令都是单字节指令,指令的操作数规定为隐含方式,在指令的书写格式中不出现,是无操作数指令。

(1)LAHF(load status flags into AH register):标志位送AH指令

格式:LAHF

操作:AH←FR的低位字节,该指令的操作如图3.15所示。

将标志寄存器FR的低8位状态标志送入AH寄存器的相应位,即SF送D7位,ZF送D6位,AF送D4位,PF送D2位,CF送D0位。LAHF指令执行以后,AH的D5、D3、D1位没有意义。

(2)SAHF(store AH into flag register):AH内容送标志寄存器指令

格式:SAHF

操作:FR的低位字节←AH

图3.15 LAHF操作情况

SAHF指令的功能正好与LAHF相反,它是将AH寄存器中的D7、D6、D4、D2、D0位的状态标志送入标志寄存器FR的相应位,而FR的其他位不受影响。

(3)PUSHF(push flags register onto stack):标志寄存器压入堆栈指令格式:PUSHF

操作:SP←SP-1,(SP)←FR的高8位字节

   SP←SP-1,(SP)←FR的低8位字节

该指令的功能是将16位标志寄存器FR的内容压入堆栈顶部进行保存,堆栈指针SP的值减2。指令执行后,FR的内容不变。其操作过程与PUSH指令类似。

(4)POPF(pop stack into flag register):堆栈内容弹出到标志寄存器指令

格式:POPF

操作:FR低8位←(SP),SP←SP+1

FR高8位←(SP),SP←SP+1

POPF指令的功能正好与PUSHF相反,它将当前栈顶中的一个字数据弹出来,送到标志寄存器FR中,同时堆栈指针SP的值加2,其操作过程与POP指令类似。

4.I/O数据传送指令

80486微处理器指令系统中的I/O指令,只能在AL或AX寄存器与输入/输出端口之间进行数据传送。输入/输出端口地址的寻址方式包括直接寻址和DX寄存器间接寻址两种。

(1)IN(input data from port):输入指令

格式:IN 累加器,端口地址

输入指令允许把一个字节或一个字数据由输入端口传送到AL(字节)或AX(字)中。若端口地址采用直接寻址方式,则由8位立即数直接给出,可寻址0~255共256个端口。若端口地址采用DX寄存器间接寻址方式,可间接寻址64K(65536)个16位端口地址。根据不同的寻址方式,输入指令如下:

    IN AL,PORT ; AL←(端口PORT)
    IN AX,POPT ; AX←(端口(PORT+1))(端口PORT)
    IN AL,DX ; AL←(端口DX)
    IN AX,DX ; AX←(端口DX+1)(端口DX)

指令中的PORT代表8位的端口地址号0~255(用十六进制数表示为00H~FFH)。

例如:IN AL,80H ; 直接寻址方式的字节型输入指令

该指令将8位端口80H中的内容传送到AL寄存器中,若端口80H中的内容为3FH,则指令执行后AL=3FH。

例如:MOV DX,2000H

   IN AX,DX ; 间接寻址方式的字类型输入指令

该指令将由(DX+1)和(DX)确定的相邻两个端口地址2001H和2000H中输入的一个字数据传送到累加器AX中。存放时,AH=(2001H),AL=(2000H),即高位对应高地址,低位对应低地址。

(2)OUT(output data to port):输出指令

格式:OUT 端口地址,累加器

输出指令把预先存放在AL中的一个字节数据或AX中的一个字数据传送到指令指定的输出端口,端口地址的寻址方式同输入指令。根据不同的寻址方式,输出指令如下:

    OUT PORT,AL ; (端口PORT)←AL
    OUT PORT,AX ; (端口PORT+1)(端口PORT)←AX
    OUT DX,AL ; (端口DX)←AL
    OUT DX,AX ; (端口DX+1)(端口DX)←AX

例如:OUT 50H,AX ; 直接寻址方式的字类型输出指令

该指令将预先存放在AX累加器中的一个字数据传送到端口51H和50H。传送时,高位对应高地址,低位对应低地址。若AX=0304H,则指令执行后,(51H)=03H,(50H)=04H。

例如:MOV AL,68H

   MOV DX,3000H

   OUT DX,AL ; 间接寻址方式的字节型输出指令

该指令可将AL中的一个字节数据68H传送到由寄存器DX确定的一个输出端口3000H。指令执行后,端口(3000H)=68H。

由以上讨论可知,输入/输出指令只能在累加器AL/AX与I/O端口之间传送数据,而不能使用其他寄存器代替。当采用直接寻址方式的指令时,寻址端口地址范围为0~FFH,一般适用于较小规模的微机系统。当需要寻址大于FFH的端口地址时,必须使用DX间接寻址。在输入/输出过程中,要搞清楚以下三方面的要求。

① 是输入过程还是输出过程(指数据传送的方向)?

② 数据是字节类型还是字类型(指输入/输出数据的类型)?

③ 是直接寻址还是DX间接寻址(指指令的寻址方式)?

明确了上述三个方面的要求后,才能正确地完成CPU与I/O端口之间的数据传送。

【例3.2】 由端口3000H输入一个字节数据到CPU内部寄存器中。

    MOV DX,3000H
    IN AL,DX

若由同一端口输入一个字数据,则可将寄存器AL改为AX。

【例3.3】 将数据3A3BH输出到端口20H。

    MOV AX,3A3BH
    OUT 20H,AX

3.3.2 算术运算类指令

80486微处理器的算术运算类指令包括二进制数运算指令及十进制数运算指令两种。指令系统中提供了加、减、乘、除4种基本算术操作,用于字节或字的运算、有符号数和无符号数的运算。如果是有符号数,则用补码来表示。指令系统中还提供了各种校正操作指令,可以进行BCD码或ASCII码表示的十进制数的算术运算。在学习算术运算类指令的过程中,除掌握指令的格式、操作功能外,还要掌握指令对标志位的影响。80486微处理器算术运算指令见表3.2。

1.加法指令

加法指令共有三条。

(1)ADD(signed or unsigned ADD):不带进位的加法指令

格式:ADD d,s

操作:(d)←(d)+(s)

其中,目标操作数d为被加数操作数和结果(即和)操作数,源操作数s为加数操作数。

表3.2 80486指令系统的算术运算类指令

注:s表示源操作数,↑表示运算结果影响标志位,d表示目标操作数,·表示运算结果不影响标志位x表示标志位为任意值,1表示将标志位置1,0表示将标志位清0

ADD指令的功能:将源操作数与目标操作数相加,结果存放在目标操作数中,并根据结果置标志位。ADD指令完成半加器的功能。

源操作数可以是8/16位的通用寄存器操作数、存储器操作数或立即数。目标操作数除不允许为立即数外,其他同源操作数。

注意:两个操作数不能同时为存储器操作数,段寄存器不能作为源操作数和目标操作数。

例如:ADD AL,BL

假设指令执行前,AL=66H,BL=20H。

执行指令:

指令执行后,AL=86H,BL=20H。

标志位的情况:最高位无进位,CF=0;结果不为0,ZF=0;最高位为1,SF=1;D3向D4无进位,AF=0;CS⊕CP=0⊕1=1,OF=1,发生溢出;结果中含3个1,PF=0。

例如:ADD WORD PTR [BX+106BH],1234H

注意:WORD PTR指明存储器操作数为字类型。

若 DS=2000H,BX=1200H,则字操作数存储单元的物理地址为:

物理地址=20000H+1200H+106BH=2226BH

指令执行前,(2226BH)=44H,(2226CH)=33H。

执行指令:

指令执行后,(2226BH)=78H,(2226CH)=45H。

标志位的情况:最高位无进位,CF=0;结果不为0,ZF=0;最高位为0,SF=0;D3向D4无进位,AF=0;CS⊕CP=0⊕0=0,OF=0;结果中含7个1,PF=0。

(2)ADC(add with carry):带进位的加法指令

格式:ADC d,s

操作:(d)←(d)+(s)+CF

ADC指令的操作和功能与ADD指令基本相同,唯一的不同是还要加上当前进位标志的值。ADC指令完成全加器的功能,主要用于两个多字节(或多字)二进制数的加法运算。

【例3.4】 两个无符号双精度数(双字数据)的加法。

目标操作数(被加数)存放在DX和AX寄存器中,其中DX存放高位字,AX存放低位字。源操作数(加数)存放在BX和CX寄存器中,其中BX存放高位字,CX存放低位字。

指令执行前,DX=0002H,AX=F365H,BX=0005H,CX=E024H。

应完成的操作:0002F365H+0005E024H。

双字加法指令序列为:

    ADD AX,CX ; 低位字相加
    ADC DX,BX ; 高位字带进位相加

执行第一条指令:

第一条指令执行后,AX=D389H,最高位有进位,CF=1;结果不为0,ZF=0;最高位为1,SF=1;D3向D4无进位,AF=0;CS⊕CP=1⊕1=0,OF=0;结果中含8个1,PF=1。

执行第二条指令:

第二条指令执行后,DX=0008H,最高位无进位,CF=0;结果不为0,ZF=0;最高位为0,SF=0;D3向D4无进位,AF=0;CS⊕CP=0⊕0=0,OF=0;结果中含奇数个1,PF=0。

执行指令序列后,结果存放在DX,AX中,DX=0008H,AX=D389H,结果正确。

(3)INC(increment by 1):加1指令

格式:INC d

操作:(d)←(d)+1

INC指令的功能:将目标操作数当作无符号数,将其内容加1后,再送回到目标操作数中。目标操作数可以是8/16位的通用寄存器或存储器操作数,但不允许是立即数和段寄存器。INC指令的执行不影响CF标志位,通常用于在循环过程中修改指针和循环次数。

例如:INC CX

指令执行后,将CX寄存器中的内容加1后又送回到CX中。

2.减法指令

减法指令共有5条。

(1)SUB(subtract):不带借位的减法指令

格式:SUB d,s

操作:(d)←(d)-(s)

其中,目标操作数d和源操作数s的寻址方式的规定同ADD指令。

SUB指令的功能:将目标操作数减去源操作数,结果(差)存入目标操作数中,并根据结果置标志位。与ADD指令一样,SUB指令可以是字操作,也可以是字节操作。

例如:SUB AL,[BP+8]

若SS=5000H,BP=2000H,则源操作数存储单元的物理地址为:

物理地址=SS×16+BP+8=50000H+2000H+8=52008H

指令执行前,AL=45H,(52008H)=17H。

执行指令:

指令执行后:AL=2EH,(52008H)=17H。标志位的情况:最高位无借位,CF=0;结果不为0,ZF=0;最高位为0,SF=0;D3向D4有进位,AF=1;CS⊕CP=0⊕0=0,OF=0;结果中含4个1,PF=1。

(2)SBB(subtraction with borrow):带借位的减法指令

格式:SBB d,s

操作:(d)←(d)-(s)-CF

其中,CF为当前借位标志的值。该指令的操作和功能以及两个操作数寻址方式的规定与SUB指令极为相似,唯一的不同就是SBB指令在执行减法运算时,还要减去CF的值。SBB指令执行时,用被减数(d)减去减数(s),还要减去低位字节相减时所产生的借位。在实际应用中,SBB指令主要用于两个多字节或多字二进制数的相减过程。

【例3.5】 两个无符号的双精度数(双字数据)的减法。

假设目标操作数(被减数)存放在DX和AX寄存器中,其中DX存放高位字,AX存放低位字。源操作数(减数)存放在CX和BX寄存器中,其中CX存放高位字,BX存放低位字。

指令执行前,DX=0012H,AX=7546H,CX=0010H,BX=9428H,应完成00127546H-00109428H的减法过程。

双字减法指令序列为:

    SUB AX,BX ; 低位字相减
    SBB DX,CX ; 高位字连同借位CF相减

执行第一条指令:

第一条指令执行后,AX=E11EH。最高位有借位,CF=1;结果不为0,ZF=0;最高位为1,SF=1;D3向D4有借位,AF=1;CS⊕CP=1⊕0=1,OF=1,发生溢出;结果中含8个1,PF=1。

执行第二条指令:

第二条指令执行后,DX=0001H。最高位无借位,CF=0;结果不为0,ZF=0;最高位为0,SF=0;D3向D4无借位,AF=0;CS⊕CP=0⊕0=0,OF=0;结果中含一个1,PF=0。

该指令序列执行后,差存放在DX,AX中,DX=0001H,AX=E11EH。

(3)DEC(decrement by 1):减1指令

格式:DEC d

操作:(d)←(d)-1

DEC指令的功能以及操作数的规定与INC指令基本相同,所不同的只是将目标操作数的内容减1,结果回送到目标操作数中。与INC指令一样,DEC指令通常用于在循环过程中修改指针和循环次数。

例如:DEC CX

指令执行后,将CX寄存器中原先的内容减1后又回送到CX中。

(4)NEG(two’s complement negate):求补指令

格式:NEG d

操作:

NEG指令的功能:将目标操作数的内容按位求反后末位加1,再回送到目标操作数中。对一个操作数求补实际上也相当于用0减去该操作数,则NEG指令执行的也是减法(d)←0-(d)。其目标操作数的规定同INC、DEC指令。

例如:NEG DL

指令执行前,DL=80H。

指令执行:

指令执行后:DL=80H,CF=1,ZF=0,SF=1,AF=0,OF=1,PF=0。

(5)CMP(compare two operands):比较指令

格式:CMP d,s

操作:(d)-(s)

CMP指令的功能、操作数的规定以及影响标志位的情况类似于SUB指令。唯一的不同是CMP指令不保存相减以后的结果(差),即该指令执行后,两个操作数原先的内容不会改变,只是根据相减操作的结果设置标志位。CMP指令通常用于在分支程序结构中比较两个数的大小,在该指令之后经常安排一条条件转移指令,根据比较的结果让程序转移到相应的分支去执行。

例如:CMP AL,CL

指令执行前,AL=68H,CL=9AH。

指令执行:

指令执行后,AL=68H,CL=9AH,最高位有借位,CF=1;结果不为0,ZF=0;最高位为1,SF=1;D3向D4有借位,AF=1;CS⊕CP=1⊕0=1,OF=1;结果中有5个1,PF=0。

作为无符号数比较时,被减数小于减数,不够减,有借位,CF=1。作为有符号数时,结果已超出有符号数所能表示的范围,因此,OF=1,有溢出。

3.乘法指令

乘法共有两条指令。

(1)MUL(unsigned multiply):无符号数的乘法指令

格式:MUL s

操作:s为字节操作数:AL×(s)→AX

   s为字操作数:AX×(s)→DX,AX

MUL指令中仅有一个操作数(源操作数),表示乘数,可使用寄存器或各种寻址方式的存储器操作数,而绝对不可以使用立即数和段寄存器。指令中的目标操作数隐含在指令中且必须使用累加器(表示被乘数),字节相乘使用AL,字相乘使用AX。乘法操作的过程如图3.16所示。

图3.16 乘法指令操作

由图3.16(a)可知,两个8位数相乘时,用AL中的被乘数乘以乘数,得到的乘积为16位,存放在AX中。由图3.16(b)可知,两个16位数相乘时,用AX中的被乘数乘以乘数,得到的乘积为32位,存放在DX,AX中(DX存放结果的高位字,AX存放结果的低位字)。

(2)IMUL(signed multiply):有符号数的乘法指令格式:IMUL s

操作:s为字节操作数 AL×(s)→AX

   s为字操作数 AX×(s)→DX,AX

IMUL指令执行的操作与MUL指令基本相同,不同之处在于MUL指令中的操作数为无符号数,而IMUL指令中的操作数为有符号数。

无符号数和有符号数的乘法指令执行结果是不同的。例如:两个4位二进制数1110和0011,如果理解为无符号数使用MUL指令运算,则1110B×0011B=2AH(即十进制数的14×3=42)。如果理解为有符号数用IMUL指令运算,则1110还原的原码为1010B(即十进制数的-2),0011B的原码仍为0011B(十进制数的+3)。运算时,先去掉符号位,将两个数的绝对值相乘,0010B×0011B=00000110B,其结果的符号按两个数符号位“异或”运算规则确定,1⊕0=1,结果为负,再将相乘所得的结果取补码。所以,最后相乘的结果为11111010B=FAH(十进制数-2×3=-6)。

乘法指令的操作影响OF和CF标志位,对其余的标志位无定义(指令执行后,这些标志位的状态不确定)。对于MUL指令,如果乘积的高一半数位为0,即字节操作时AH=0,字操作时DX=0,则操作结果使CF=0,OF=0。否则,当AH≠0或DX≠0时,则CF=1,OF=1,这种情况的标志位状态可以用来检查字节相乘的结果是字节还是字,字相乘的结果是字还是双字。而对于IMUL指令,如果乘积的高一半数位是低一半符号位的扩展,则CF=0,OF=0;否则,CF=1,OF=1。

例如:MUL BL

指令执行前,AL=B4H=180,BL=11H=17,均为无符号数

指令执行:

指令执行后,AX=0BF4H=3060,BL=11H,CF=1,OF=1。

例如:IMUL BL

指令执行前,AL=B4H=-76,为有符号数,BL=11H=17。

指令执行时,先将AL和BL中的内容转换为原码并将符号位去掉(AL=4CH),数值部分相乘。

再求结果的符号,1-0=1,即乘积应该为负数,将结果取补码[050CH]补=FAF4H=-1292,BL=11H,CF=1,OF=1。

4.除法指令

除法指令共有4条。

(1)DIV(unsigned divide):无符号数的除法指令

格式:DIV s

操作:分为字节和字两种操作类型。

字节操作时,16位被除数在AX中,8位除数为源操作数,结果的8位商在AL中,8位余数在AH中,表示为:

    AX/(s)→AL 商
    AX/(s)→AH 余数

字操作时,32位被除数在DX和AX中,其中DX为高位字,16位除数为源操作数,结果的16位商在AX中,16位余数在DX中,表示为:

    DX,AX/(s)→ AX 商
    DX,AX/(s)→ DX 余数

DIV指令的被除数、除数、商和余数全部为无符号数。

(2)IDIV(signed divide):有符号数的除法指令

格式:IDIV s

操作:与DIV指令相同,只是被除数、除数、商和余数均为有符号数,且余数的符号和被除数的符号相同。

这两条除法指令中操作数s的规定与乘法指令相同。除法指令的操作过程如图3.17所示。

图3.17 除法指令操作过程

除法指令执行后,标志位AF、OF、CF、PF、SF和ZF都是不确定的。

使用IDIV指令时,如果一个双字除以一个字,则商的范围为-32728~+32727。如果一个字除以一个字节,则商的范围为-128~+127。如果超出这个范围,那么会产生0型中断,以除数为0的情况来处理,而不是使溢出标志OF置1。

与有符号数的乘法指令类似,执行IDIV指令时,先将操作数变为原码,并去掉符号位,然后再将两个数(绝对值)相除。其结果是,商的符号按两个数符号位“异或”运算规则确定,若符号位为1(负数),再取补码。

由于除法指令的字节操作要求被除数为16位,字操作要求被除数为32位,因此,当实际数据不满足以上要求时,就需要进行被除数位数的扩展。

对于无符号数除法指令DIV来说,只需将字节操作时被除数的高8位AH和字操作时被除数的高16位DX清0即可。

对于有符号数除法指令IDIV来说,AH和DX的扩展是将其低位字节或低位字的符号位扩展。即把AL中的最高位扩展到AH的8位中(正数为00H,负数为FFH),或者把AX中的最高位扩展到DX的16位中(正数为0000H,负数为FFFFH)。为此,80486微处理器指令系统提供了专门的符号扩展指令CBW和CWD。

(3)CBW(convert byte to word):字节转换为字指令

格式:CBW

操作:AL中的符号位(最高位D7)扩展到AH中。若D7=0,则AH=00H;若D7=1,则AH=FFH。

(4)CWD(convert word to doubleword):字转换为双字指令

格式:CWD

操作:AX中的符号位(最高位D15)扩展到DX中。若D15=0,则DX=0000H;若D15=1,则DX=FFFFH。

CBW和CWD指令的执行结果都不影响标志位。

下面举例说明除法指令的用法。

例如:DIV BL

指令执行前,AX=0400H=1024,BL=B4H=180,均为无符号数。

指令执行:

指令执行后,AL=05H=5(商),AH=7CH=124(余数)。

例如:IDIV BL

指令执行前,AX=0400H=+1024,BL=B4H=-76,均为有符号数。

指令执行,先将BL中的内容转换为原码,去掉符号位,BL=4CH,数值部分相除:

数值相除后,求商的符号,0⊕1=1,即商为负数,对商再求补码得F3H,余数的符号与被除数相同。

指令执行后,AL=F3H=-13(商),AH=24H=36(余数)。

下面是用加、减、乘、除指令进行算术运算的例子。

【例3.6】 编程计算(V-((X×Y)+Z-540))÷X=?

其中,XYZV均为16位有符号数,已分别装入内存的X、Y、Z、V字单元中。要求将上式计算得到的结果存入AX中,余数存入DX中。现编制程序如下:

    MOV    AX,X   ; 将被乘数X存入AX中
    IMUL   Y       ; DX,AX←X*Y
    MOV    CX,AX
    MOV    BX,DX  ; 将乘积存入BX,CX中
    MOV    AX,Z   ; 将加数Z存入AX中
    CWD            ; 将加数扩展为双字存入DX,AX中
    ADD    CX,AX
    ADC    BX,DX  ; 完成X*Y+Z并将结果存入BX,CX中
    SUB    CX,540
    SBB    BX,0   ; 完成X*Y+Z-540,结果存入BX,CX中
    MOV    AX,V   ; 将V存入AX
    CWD            ; 将V扩展为双字数据存入DX,AX中
    SUB    AX,CX
    SBB    DX,BX  ; 完成V-(X*Y+Z-540),并将结果存入DX,AX中
    IDIV   X       ; 完成(V-(X*Y+Z-540))/X运算,并将结果存入DX,AX中

5.十进制调整指令

前面介绍过的算术运算指令都是二进制数的运算指令,如果要进行十进制数的运算,必须先把十进制数转换为二进制数,用相应的二进制数运算指令进行运算,然后再将运算得到的二进制数结果转换为十进制数加以输出。为了便于十进制数的运算,80486指令系统提供了一组专门用于十进制调整的指令,可将由二进制数运算指令得到的结果进行调整,从而得到十进制数的结果。

表示十进制数的BCD码分为两种:压缩BCD码和非压缩BCD码。

压缩的BCD码用4位二进制数表示一个十进制数位,整个十进制数形式为一个顺序的以4位为一组的数串。

例如,十进制数8564的压缩BCD码形式为:

用十六进制数表示为8564H。

非压缩的BCD码以8位二进制数为一组,表示一个十进制数位,8位中的低4位表示一位8421BCD码,而高4位则没有意义,通常将高4位清0。例如:8564的非压缩BCD码形式为:

用十六进制数表示为08050604H,为4字节数据。

由于数字0~9的ASCII码其高4位为0011B,低4位是以8421BCD码表示的十进制数位,符合非压缩BCD码高4位无意义的规定。

用普通二进制数运算指令对BCD码运算时,为什么要进行调整?怎样进行调整?下面通过加法来说明十进制调整的原理。

【例3.7】 实现7+6=13。

即BCD码的7加6,结果为十六进制数D。在BCD码中,只允许出现0~9共10个数字,D不代表任何BCD码,因此,必须对其进行转换,即进行调整。

如何调整?我们知道,BCD码运算的进位规则是“逢十进一”,但80486指令系统中的加法指令进行的是二进制数运算,对于4位二进制数运算来说,是“逢十六进一”。用这些加法指令进行十进制数运算时,必须跳过6个数的编码1010~1111。这些数在二进制数中是存在的,而在BCD码中是不存在的。因此在调整过程中,遇到运算结果中出现1010~1111时,就必须加0110(6)进行调整,让其产生进位,从而得到正确的十进制数结果。对上述结果进行加6调整,则:

【例3.8】 实现28+39=67。

用压缩BCD码表示,并用二进制数加法指令相加的过程如下:

若将结果看成BCD码,则表示十进制数61,显然是错误的。其原因是,计算机在运算时,要使低4位向高4位进位,二进制数必须是逢16才能进位,而BCD码是逢十进一,所以当BCD码按照二进制数运算规则进行计算时,只要产生了半进位,就会“暗中”丢失一个6,因此,必须在结果的低4位进行加6调整,才能得到正确的结果。对上述结果进行加6调整,则:

【例3.9】 实现80+94=174。

将两个加数用压缩BCD码表示,并用二进制数加法指令相加,过程如下:

表示十进制数结果114,而不是174,错误的原因是高4位产生进位CF=1,所以应该对运算结果的高4位加6调整,则

由此可知:当BCD码的数位增多需要进行多字节BCD码加法时,调整的原理是一样的。凡是遇上某4位二进制数值大于9时,则进行加6调整。凡是一个字节中的低4位向高4位产生进位(AF=1),或者是低位字节向高位字节产生进位(CF=1)时,也应进行加6调整。十进制调整指令会根据AF或CF的状态做出判断,看是否需要进行加6调整。

如果是进行多字节BCD码减法,则相应地进行减6调整,乘、除法也有相应的调整办法。

(1)压缩BCD码调整指令

① DAA(decimal adjust AL after addition):加法的十进制调整指令。

格式:DAA

操作:DAA指令必须紧跟在二进制数加法指令ADD或ADC之后,将二进制数加法的结果(必须放在AL中)调整为压缩的BCD码格式,再存入AL中。可见,二进制数加法指令ADD/ADC和DAA指令构成复合的压缩BCD码加法指令。

DAA指令的调整方法:考察结果(在AL中)的低4位和高4位的值以及半进位标志AF和进位标志CF的状态,如果结果的低4位的值大于9或AF=1,则将结果的低4位进行加6调整,并将AF标志位置1。如果结果的高4位的值大于9或CF=1,则将结果的高4位进行加6调整,并将CF标志位置1;如果结果的高4位和低4位的值均大于9或既有AF=1,又有CF=1时,则将结果的高、低4位均进行加6调整,并将AF、CF标志位均置1,从而得到正确的压缩BCD码结果。

DAA指令对OF标志无定义,但却影响其他所有标志。

例如:ADD AL,DL

   DAA

指令执行前:AL=27H,DL=49H。

执行ADD指令:

由于ADD指令执行后,得到的是二进制数结果,不是压缩BCD码,又有AF=1,必须进行十进制调整。

执行DAA指令:

此时,AL=76H是压缩的BCD码加法,结果为76(27+49=76)。

【例3.10】 假设(BCD1)=1834H,(BCD2)=2789H,要求将这两个十进制数相加,相加的和存入BCD3中,即 (BCD1)+(BCD2)→(BCD3)。

BCD1和BCD2都是4位压缩的BCD码,每个数都占有两个字节单元,高位字节数据存放在高地址单元中,低位字节数据存放在低地址单元中,其存放方式为:

(BCD1+1)=18H,(BCD1)=34H

(BCD2+1)=27H,(BCD2)=89H

根据题目要求编写出4位十进制数相加的程序如下:

   MOV  AL,BCD1   ; 取第一个数低2位BCD码送AL
   ADD  AL,BCD2   ; 两个数低2位BCD码二进制数加法运算,结果送AL
   DAA             ; 十进制调整,得到BCD码结果
   MOV  BCD3,AL   ; 存低2位BCD码结果到BCD3码单元中
   MOV  AL,BCD1+1 ; 取第一个数高2位BCD码送AL
   ADC  AL,BCD2+1 ; 两个数高2位BCD码带低位进位相加,得到二进制数结果
   DAA             ; 十进制调整,得到BCD码结果
   MOV  BCD3+1,AL ; 存高2位BCD码结果到BCD3+1单元中

本程序共有8条指令,分成两组。第一组前4条指令,完成两个加数低2位BCD码相加,经十进制调整后存入DCD3中。其中,ADD指令执行后,AL=BDH,CF=0,AF=0,经DAA指令调整后,AL=23H,CF=1,AF=1。

第二组后4条指令,完成两个加数高2位BCD码带进位相加,经十进制调整后存入BCD3+1单元中。其中,ADC指令执行后,AL=40H,CF=0,AF=1,经DAA指令执行后,AL=46H,CF=0,AF=0。

程序执行后,字单元(BCD3)=4623H,结果正确。

② DAS(decimal adjust AL after subtraction):减法的十进制调整指令。

格式:DAS

操作:DAS指令必须紧跟在二进制数减法指令SUB或SBB指令之后,将二进制数减法的结果(必须放在AL中)调整为压缩的BCD码格式,再存入AL中。可见,二进制数减法指令SUB/SBB和DAS指令构成了复合的压缩BCD码减法指令。

DAS指令的调整方法类似于DAA,只是在进行十进制调整时,DAA指令是加6调整,而DAS指令是减6调整。对标志位的影响也同DAA指令。

例如:SUB AL,AH

   DAS

若指令执行前:AL=88H,AH=49H。

执行SUB指令后,AL=3FH,AF=1。执行DAS指令,因为AF=1,需要减06H进行调整。

指令执行后,AL=39H,是压缩的BCD码减法结果39(88-49=39)。

(2)非压缩的BCD码调整指令

① AAA(ASCII adjust after addition):加法的ASCII码调整指令。

格式:AAA

操作:与DAA指令一样,AAA指令也必须紧跟在二进制数加法指令ADD/ADC之后,先用二进制数加法指令将两个非压缩的BCD码相加,并把结果存入AL寄存器中。

指令执行时:

AL←把AL中二进制数加法的结果调整为非压缩BCD码格式;

AH←AH+调整产生的进位值。

考察AL寄存器的低4位的值或AF标志,一旦发现其低4位的值大于9或AF=1,则将AL寄存器的内容进行加6调整,AH寄存器的内容加1,将AF标志位置1,清除AL寄存器的高4位,并将AF的值送给CF。AAA指令影响AF和CF标志位,对其余的标志位无效。

例如:ADD AL,CL

   AAA

指令执行前,AX=0035H,CL=38H,为数字5和8的ASCII码,相加结果应为13。但是执行ADD指令后,AL=6DH,AF=0,需要进行加6调整。执行AAA指令进行调整,清除AL的高4位,低4位加06H。调整后,AL高4位为1,AH的内容加1,并将AF的值送给CF。所以,AX=0103H,即非压缩BCD码的13,AF=1,CF=1。

② AAS(ASCII adjust AL after subtraction):减法的ASCII码调整指令。

格式:AAS

操作:和DAS指令一样,AAS必须紧跟在减法指令SUB或SBB之后,先用二进制数减法指令将两个非压缩的BCD码相减,并将结果存入AL寄存器中。

AAS指令执行时:

  AL←把AL中减法结果调整为压缩的BCD格式;

  AH←AH-调整产生的借位值。

考察AL寄存器的低4位的值或AF标志,一旦发现其低4位的值大于9或AF=1,则把AL寄存器的内容减6,AH寄存器的内容减1,将AF标志位置1,清除AL寄存器的高4位,并将AF的值送给CF。AAS指令操作对标志位的影响同AAA指令。

例如:SUB AL,DL

   AAS

指令执行前,AX=0236H,DL=39H,寄存器AL和DL的内容分别为数字6和9的ASCII码。

SUB指令执行后,AL=FDH,AF=1。

AAS指令执行后,AL减06H调整,AH减1,同时清除AL高4位,则AX=0107H,CF=1,AF=1。

③ AAM(ASCII adjust AX after multiplication):乘法的ASCII码调整指令。

格式:AAM

操作:AX←把AL中的二进制乘积调整为非压缩的BCD格式

在AAM指令执行前,必须先执行MUL指令,将两个非压缩的BCD码相乘(此时要求其高4位为0),结果放在AL寄存器中。AAM指令的调整方法:将AL寄存器的内容除以0AH,所得的商(高位十进制数)保存在AH寄存器中,余数(为低位十进制数)保存在AL寄存器中。AAM指令影响标志位SF、ZF和PF,但对OF、CF和AF位无意义。

例如:MUL BL

   AAM

指令执行前,AL=07H,BL=09H。

MUL指令执行后,AL=3FH。

AAM指令执行后,AH=06H,AL=03H。

④ AAD(ASCII adjust AX before division):除法的ASCII码调整指令。

格式:AAD

操作:AL←10×AH+AL

   AH←0

前面所述的加法、减法和乘法的ASCII码调整指令都是用加法、减法和乘法指令对两个非压缩的BCD码运算之后,再使用AAA、AAS、AAM指令来对运算结果进行十进制调整的,而AAD指令则是在除法之前先进行调整操作。

AAD指令的调整操作是,将预先存放在AX中的两位非压缩BCD码的十进制数调整为二进制数,存放在AL中。具体做法是,将AH中的高位十进制数乘以10,与AL中的低位十进制数相加,结果以二进制数形式保留在AL中,然后将AH清0。

例如:AAD

指令执行前,AX=0607H。

操作过程:AL=(AH)×10+(AL)

      =06×10+07

      =67

      =43H

指令执行后,AX=0043H。

3.3.3 逻辑运算与移位类指令

逻辑运算与移位类指令可分为三种类型,见表3.3。

表3.3 逻辑运算与移位类指令

注:表中对标志位影响的符号含义同表3.2。

1.逻辑运算指令

逻辑运算指令共有5条。

(1)AND(logical AND):逻辑“与”指令

格式:AND d,s

操作:(d)←(d)∧(s)

其中,符号“∧”表示逻辑“与”操作。源操作数s可以是8/16位通用寄存器、存储器操作数或立即数,而目标操作数d只允许是通用寄存器或存储器操作数。

AND指令可将两个操作数的内容按位相“与”,并将结果保存到目标操作数中。指令执行后,将使CF=0,OF=0,AF位无定义,并影响SF、ZF和PF标志位。

AND指令常用于将操作数的某些位清0(也称为屏蔽某些位),而其余位维持不变。需要清0的位和0相“与”,需要维持不变的位和1相“与”。

【例3.11】 将AL寄存器中的D1位、D5位清0,其余位保持不变。

AND AL,0DDH ; 将D1位、D5位和0“与”,其他位和1“与”

指令执行前,AL=7AH。

指令执行:

指令执行后,AL=58H。

(2)OR(logical OR):逻辑“或”指令

格式:OR d,s

操作:(d)←(d)∨(s)

其中,符号“∨”表示逻辑“或”操作。源操作数和目标操作数的约定同AND指令。

OR指令可将两个操作数的内容按位相“或”,并将结果保存到目标操作数中。对标志位的影响同AND指令。

利用OR指令可将操作数的某些位置1,而其余位不变。需要置1的位和1相“或”,需要维持不变的位和0相“或”。

例如:OR AL,80H

上述指令可以使AL寄存器中的最高位置1,其余位不变。

指令执行前,AL=3AH。

指令执行:

指令执行后,AL=BAH。

(3)XOR(logical exclusive OR):逻辑“异或”指令

格式:XOR d,s

操作:(d)←(d)⊕(s)

其中,符号“⊕”表示逻辑“异或”操作。源操作数和目标操作数的约定同AND指令。

XOR指令可将两个操作数按位相“异或”,并将结果保存到目标操作数中。对标志位的影响同AND指令。

利用XOR指令,可将操作数的某些位求反,某些位不变。维持不变的位与0相“异或”,需要求反的位与1相“异或”。

例如:XOR BL,0FH

该指令可使BL寄存器的高4位维持不变,而将低4位求反。

指令执行前,BL=55H。

指令执行:

指令执行后,BL=5AH。

例如:XOR AL,AL

指令执行前,AL=78H。

指令执行:

指令执行后,AL=00H,CF=0,ZF=1。

可见,XOR指令可用于将操作数清0,并使CF=0。

(4)NOT(logical NOT):逻辑“非”指令

格式:NOT d

操作:

其中,操作数d上面的“-”表示求反运算,有关操作数的约定同AND指令。

NOT指令可将操作数的内容按位求反,并将结果保存到源操作数中,其执行结果不影响任何标志位。

例如:NOT AL

指令执行前,AL=33H。

指令执行后,AL=CCH。

(5)TEST(test bits):测试指令

格式:TEST d,s

操作:(d)∧(s)

TEST指令完成的操作、操作数的约定,以及对标志位的影响同AND指令,只是TEST指令不回送结果到目标操作数中。

使用TEST指令,通常是在不希望改变原有操作数的情况下,检测某一位或某几位的状态,所以常被用于条件转移指令之前,根据测试的结果令程序发生跳转。

【例3.12】 检测DL中的最高位是否为1。若为1,则转移到标号LOP1去执行;否则顺序执行。

可用下列程序实现:

       TEST DL,80H
       JNZ LOP1
       …
LOP1: MOV AL,BL
       …

2.移位与循环移位指令

移位与循环移位指令共有8条,其功能如图3.18所示。指令中的目标操作数d可以是8/16位通用寄存器和存储器操作数,不允许使用立即数和段寄存器。移位次数由count决定。count可取1或CL寄存器操作数。当count为1时,每执行一条指令,可将操作数的内容移1位;若需要移位的次数大于1,则必须在移位指令之前,将移位次数置于CL中,而在移位指令中将count写为CL,当移位结束后,CL=0。

(1)移位指令

移位指令共有4条。

① SHL(shift left):逻辑左移指令

格式:SHL d,count

操作:SHL指令可将目标操作数的内容向左移位,移位的次数由count给定,每左移1位,操作数最高位的状态移入CF标志位,末位补0。

图3.18 移位与循环移位指令功能

例如:MOV CL,4

   SHL AL,CL

SHL指令执行后,使AL的内容左移4位,即AL中的低4位的状态移入高4位,并将低4位清0。

② SHR(shift right):逻辑右移指令

格式:SHR d,count

操作:SHR指令的操作和SHL指令相反,将目标操作数的内容向右移位,每右移1位,操作数最末位移入CF标志,最高位补0。

③ SAL(shift arithmetic left):算术左移指令

格式:SAL d,count

操作:与SHL指令完全相同。

④ SAR(shift arithmetic right):算术右移指令

格式:SAR d,count

操作:将目标操作数的内容向右移位,每右移1位,操作数最末位移入CF标志位,最高位移入次高位的同时其值不变,这样,移位后最高位和次高位的值相同,符号位始终保持不变。

综上所述,移位指令分为算术移位指令和逻辑移位指令。算术移位指令只对有符号数进行移位,在移位过程中保持符号位不变。而逻辑移位指令对无符号数移位,移位时,总是用0来填补已空出的数位。每左移1位,相当于将原数据乘以2,每右移1位,相当于将原数据除以2。根据移位操作的结果置标志寄存器中的状态标志(AF位除外)。若移位的次数是1,移位的结果又使最高位(符号位)发生变化,则将溢出标志OF置1。移多位时,OF标志无效。这样,对于有符号数而言,可由此判断移位后的符号位和移位前的符号位是否相同。

(2)循环移位指令

循环移位指令共有4条。

① ROL(rotate left):循环左移指令

格式:ROL d,count

操作:每左移1位,目标操作数最高位的状态移出,该状态除送入标志位CF外,还循环传递到由于左移1位而空出的目标操作数最末位。

② ROR(rotate right):循环右移指令

格式:ROR d,count

操作:ROR指令的操作正好和ROL指令相反,每右移1位,将目标操作数最末位的状态移出,并传递到CF标志和目标操作数的最高位。

③ RCL(rotate left through carry):带进位循环左移指令

格式:RCL d,count

操作:每左移1位,将目标操作数最高位的状态移入CF标志位,而CF标志原先的状态移入目标操作数最末位。

④ RCR(rotate right through carry):带进位循环右移指令

格式:RCR d,count

操作:RCR指令完成的操作和RCL指令正好相反。每右移1位,将目标操作数最末位的状态移入CF标志,而CF标志原先的状态移入目标操作数最高位。

综上所述,循环移位指令有两类。ROL和ROR指令在执行时,没有把CF套在循环中,称为小循环移位。而RCL和RCR指令在执行时,连同CF一起进行循环移位,称为大循环移位。以上4条指令仅影响标志位CF和OF。对OF的影响表现为,ROL和RCL指令在执行一次左移后,如果目标操作数的最高位与CF(原先的符号位)不等,说明新的符号位与原来的符号位不同了,则使OF=1,表明左移循环操作造成了溢出。同样,ROR和RCR指令在执行一次右移后,如果目标操作数的最高位和次高位不等,也表明移位后新的数据符号与原来的符号不同了,此时也会使OF=1,产生溢出。

【例3.13】 若X为字数据,已存放在寄存器AX中,现要求将该数乘以10。

在80486指令系统中有乘法指令,但从指令手册中可知,执行乘法指令所花的时间为10~11个时钟周期。而用移位指令实现乘2操作所需要的时间为1~3个时钟周期,从而可提高计算速度3~10倍。

很明显,X×10=X×2+X×8。某数乘2即左移1位,乘8即左移3位。程序如下:

    SAL AX,1  ; X*2
    MOV BX,AX ; X*2的乘积存入BX
    SAL AX,1  ; X*4
    SAL AX,1  ; X*8
    ADD AX,BX ; X*8+X*2=X*10

执行上述程序段所需要的时间为5个时钟周期。若改用乘法指令实现乘10操作,则所需时间为11个时钟周期。

3.3.4 串操作类指令

80486指令系统提供了一组强有力的串操作指令。串操作指令可以对一系列含有字母、数字的字节(也称字符串)进行操作和处理,例如,传送、比较、查找、插入、删除操作等。

串操作指令是指令系统中唯一可以在存储器内部进行源操作数与目标操作数之间的操作的指令,所有串操作指令均可以处理字或字节。串操作指令见表3.4。

为缩短指令长度,串操作指令均采用隐含寻址方式。一般,源串存放在当前数据段中,由DS段寄存器提供段基址,其偏移地址必须由源变址寄存器SI提供。目标串必须存放在附加段中,由ES段寄存器提供段基址,其偏移地址必须由目标变址寄存器DI提供。如果要在同一段内进行串操作,必须使DS和ES指向同一段。字符串长度必须存放在CX寄存器中。因此,在串指令执行之前,必须对SI、DI和CX预置初值,即将源串和目标串的首元素或末元素的偏移地址分别置入SI和DI中,将字符串长度置入CX中。这样,CPU每处理完一个字符串元素,就自动修改SI和DI寄存器的内容,使之指向下一个元素。

表3.4 串操作指令

注:表中对标志位影响的符号含义同表3.2。

为加快串操作的执行速度,可在串操作指令前加上重复前缀,共有5种重复前缀。带有重复前缀的串操作指令,每处理完一个字符串元素后自动修改CX的内容(按字节/字处理,减1或减2),以完成计数功能。当CX≠0时,继续串操作,直到CX=0时才结束操作。

串操作指令对SI和DI寄存器的修改与两个因素有关。①与被处理的字符串是字节串还是字串有关。②与当前标志寄存器中DF的状态有关。当DF=0时,表示串操作由低地址向高地址进行,SI和DI内容递增,其初始值应该是源串和目标串的首地址;当DF=1时,则情况正好相反。

1.字符串操作指令

80486指令系统中共有5种串操作指令,下面分别予以介绍。

(1)MOVS(move data from string to string):串传送指令

格式(有三种形式):

    MOVS  d,s
    MOVSB       ; 字节串传送
    MOVSW       ; 字串传送

操作:① (DI)←(SI)

② 若为字节操作:SI←SI±1,DI←DI±1

当方向标志DF=0时,用“+”;当方向标志DF=1时,用“-”。

③ 若为字操作:SI←SI±2,DI←DI±2

当方向标志DF=0时,用“+”;当方向标志DF=1时,用“-”。

MOVS指令可以把一个字节或一个字从源串(由SI寻址)传送到目标串(由DI寻址)中,并自动修改SI和DI的值,使之指向下一个字符串元素。MOVSB/MOVSW是MOVS的替代符,由于指令助记符中已明确是字节串还是字串传送,因此没有操作数。通常,指令前要加上重复前缀REP,此时,要传送的字符个数在CX中,每传送完一个元素,CPU自动修改CX的值(按字节/字处理,减1或减2),直到CX=0为止,从而完成从存储器到存储器的字符串成块传送。串传送指令的结果不影响标志位。

例如,设变量ADDR1和ADDR2为字类型,下面两条指令是等效的:

    MOVS  ADDR1,ADDR2
    MOVSW

【例3.14】 将源数据串的256个字节数据传送到目标串的单元中。源数据串的段首地址的偏移地址为2000H,目标串首地址的偏移地址为5000H。

完成数据串传送的程序段如下:

    CLD           ; DF=0,地址自动递增
    MOV CX,256   ; 设置计数器,初始值为数据串的长度,CX←256
    MOV SI,2000H ; 源数据串首元素的偏移地址,SI←2000H
    MOV DI,5000H ; 目标数据串首元素的偏移地址,DI←5000H
    REP MOVSB     ; 重复串操作,直到CX=0为止

(2)CMPS(compare string operands):串比较指令

格式(有三种形式):

    CMPS s,d
    CMPSB
    CMPSW

字节操作:(SI)-(DI),SI←SI±1,DI←DI±1

字操作:(SI)-(DI),SI←SI±2,DI←DI±2

串比较指令将由SI作为指针的源串中的一个元素减去由DI作为指针的目标串中相对应的一个元素,不回送结果,只根据结果特征置标志位,并相应地修改SI和DI的值,使之指向下一个元素。指令的其他特性与MOVS指令的相同。通常,在CMPS指令前加重复前缀REPE/REPZ,两者的定义完全相同,只是书写的形式不一样而已。此时,可重复进行两数的比较。仅当ZF=1(两数相等)且CX≠0(元素未比较结束)时,才可继续进行比较,一旦发现ZF=0(两数不相等或元素比较结束),则终止指令的执行。

注意:串比较指令的源串操作数(由SI寻址)是写在逗号左边的,而目标串操作数(用DI寻址)写在右边。这是指令系统中唯一例外的指令句法结构,编程时要特别注意。CMPSB和CMPSW同样作为CMPS指令的替代符,不带操作数。

(3)SCAS(scan byte or word string):搜索指令

格式(有三种形式):

    SCAS  d
    SCASB     ; 字节串搜索
    SCASW     ; 字串搜索

字节操作:AL-(DI),DI←DI±1

字操作:AX-(DI),DI←DI±2

搜索指令用来从目标串中查找某个关键字,要求将待查找的关键字预先置入AX(字)或AL(字节)中。指令执行时,将AX或AL中的关键字减去由DI指向的目标串中的一个元素,不传送结果,只根据结果置标志位,然后修改DI的值使之指向下一个元素。通常,在SCAS前加重复前缀REPNE/REPNZ,可重复进行在目标串中寻找关键字的操作,一直到ZF=1(查到了某关键字)或CX=0(终未查找到)为止。同样,SCASB和SCASW为SCAS指令的替代符,不带操作数。

【例3.15】 从段首元素的偏移地址为0100H的一个字符串(字符个数为256)中找出指定的字符(如$),可用REPNZ SCASB指令实现。

程序如下:

CLD           ; DF=0,地址自动递增
MOV DI,0100H ; 目标串首元素的偏移地址,DI←0100H
MOV CX,256   ; 设置计数器初始值,CX← 256
MOV AL,'$'   ; 设关键字,AL←24H($的ASCII码)
REPNZ SCASB   ; 找关键字,若未找到则重复查找

(4)LODS(load byte or word string):读字符串指令

格式(有三种形式):

LODS s
LODSB   ; 读取字节串
LODSW   ; 读取字串

字节操作:AL←(SI),SI←SI±1

字操作:AX←(SI),SI←SI±2

读字符串指令把由SI指定的数据段中字节或字单元的内容送入AL或AX中,并根据方向标志DF及数据类型来修改SI的值。

指令执行之前,要取出的数据必须在存储器中预先定义(用DB或DW),SI必须预置初始值。读字符串指令一般不加重复前缀,常用来和其他指令相结合完成复杂的串操作功能。而LODSB和LODSW同样为LODS的替代符,不带操作数。读字符串指令的执行结果不影响标志位。

(5)STOS(store byte or word string):写字符串指令

格式(有三种形式):

STOS  d
STOSB   ; 写入字节串
STOSW   ; 写入字串

字节操作:(DI)←AL,DI←DI±1

字操作:(DI)←AX,DI←DI±2

写字符串指令将AL或AX中的内容存入由DI指定的附加段中的字节或字单元中,并根据DF的值及数据类型来修改DI的值。

指令执行之前,必须把要存放的数据预先存入AX或AL中,并对DI预置初始值。写字符串指令的执行结果也不影响标志位。STOSB和STOSW同样为STOS的替代符,不带操作数。如果加上前缀指令REP,则用STOSB和STOSW可使一串内存单元填满相同的数。

【例3.16】 将字符'$'送入附加段中偏移地址为0100H的连续5个单元中。

程序如下:

CLD           ; DF=0,地址自动递增
MOV CX,5     ; 设置计数器,CX←5
MOV DI,0100H ; 目标串首元素的偏移地址,DI←0100H
MOV AL,'$'   ; 设置关键字,AL←24H($的ASCII码)
REP STOSB     ; 连续将'$'写入相应存储单元中

2.重复前缀

在实际应用中,如果必须重复执行基本串操作来处理一个数据阵列,则需要在指令前加上一个重复前缀。常用的重复前缀见表3.5。

表3.5 常用的重复前缀

(1)前缀REP(repeat)

REP将重复基本操作一直到CX寄存器的值为0为止。每次执行指令时都要测试CX。如果CX≠0,则CX减1,重复基本操作。当CX=0时,重复串操作结束,执行下一条指令。在执行重复串指令前必须先将重复次数装入CX中。

(2)前缀REPE/REPZ(repeat while equal/repeat while zero)

REPE和REPZ的功能相同。它们与CMPS、SCAS指令一起使用。带前缀的CMPS/ SCAS指令,只要CX≠0且ZF=1,基本比较或扫描操作就能够一直重复执行。CX≠0表示还没有到达串尾,ZF=1表示所比较的元素相等。

(3)前缀REPNE/REPNZ(repeat while not equal/repeat while not zero)

REPNE和REPNZ的工作方式类似于REPE/REPZ,只不过重复条件变为CX≠0且ZF=0。这就是说,只要串元素不等且未到达串尾,就重复执行比较或扫描操作。

3.3.5 程序控制类指令

在一般情况下,CPU执行程序是按照指令的顺序逐条执行的,但实际上程序不可能总是顺序执行,而经常需要改变程序的执行流程,转移到所要求的目标地址去执行,这就必须安排一条程序转移类指令。在80486指令系统中,程序控制类指令就是专门用来控制程序流向的,包括无条件转移、条件转移、循环控制及中断控制4种类型。

1.无条件转移指令

无条件转移指令的功能是使程序无条件地转移到指令指定的地址去执行。它分为无条件转移、调用过程及从过程返回三种指令格式,见表3.6。

表3.6 无条件转移指令的三种指令格式

(1)JMP(unconditional jump):无条件转移指令

格式:JMP 目标标号

操作:JMP指令可以使程序无条件地转移到目标标号指定的地址去执行。目标地址可以在当前代码段内(段内转移),也可在其他代码段中(段间转移)。根据目标地址的位置和寻址方式的不同,有5种基本格式。

① 段内直接短程转移

格式:JMP SHORT 目标标号

操作:IP←IP+D8

其中,SHORT为属性操作符,表明指令代码中的操作数是一个以8位二进制补码形式表示的偏移量,它只能在-128~+127范围内取值。SHORT在指令中可以省略。执行指令时,转移的目标地址由当前的IP值(即跳转指令的下一条指令的首地址)与指令代码中8位偏移量之和决定。

【例3.17】 在当前代码段中有一条无条件转移指令如下:

      JMP SHORT LOP1
       …
LOP1:MOV AL,55H
       …

上述指令的执行过程及转移地址的形成如图3.19所示。由图3.19可知,当前IP的内容为0102H,偏移量为08H,所以,标号LOP1的偏移地址为0102H+08H=010AH。

图3.19 例3.17指令的执行过程及转移地址的形成

② 段内直接近程转移

格式:JMP NEAR PTR 目标标号

操作:IP←IP+D16

其中,NEAR PTR为近程转移的属性操作符。段内直接近程转移指令控制转移的目标地址由当前IP值与指令代码中16位偏移量之和决定,偏移量的取值范围为-32768~+32767。其转移的过程和短程转移过程基本相同,属性运算符NEAR PTR在指令中可以省略。

③ 段内间接转移

格式:JMP WORD PTR OPR

操作:IP←(EA)

其中,OPR可以是存储器或寄存器操作数。将段内转移的目标地址预先存放在某寄存器或存储器的某两个连续地址中,指令中只需给出该寄存器名或存储单元地址,这种方式称为段内间接转移(OPR为寄存器时,不加WORD PTR)。

例如:JMP BX

上述指令是由寄存器间接表示转移的目标地址。设CS=1000H,IP=3000H,BX=0102H,该指令的执行首先以寄存器BX的内容取代IP的内容,然后,CPU将转移到物理地址为CS×16+IP=10102H的单元中去执行后续指令。

例如:JMP WORD PTR [SI]

上述指令由存储单元的内容表示转移的目标地址,用该目标地址去置换IP的值,其中属性字段WORD PTR表明后面紧跟的存储器操作数是一个字类型。

设DS=2000H,SI=0100H,则存放转移地址偏移量的字单元的物理地址为DS×16+SI=20100H。

指令执行前,CS=4000H,IP=2500H,(20100H)=22H,(20101H)=30H。

指令执行后,IP=3022H,程序转移到43022H地址处继续执行。

以上3种转移方式均为段内转移。在指令执行时,用指令提供的信息修改指令指针IP的内容,CS的值不变。

④ 段间直接转移

格式:JMP FAR PTR 目标标号

操作:IP←目标标号的偏移地址

CS←目标标号所在段的段基址

其中,FAR PTR为属性运算符,表示转移是在段间进行的。目标标号在其他代码段中,指令中直接给出目标标号的段基址和偏移地址,分别取代当前IP及CS的值,从而,转移到另一代码段中相应的位置去执行(FAR PTR在指令中可以省略)。

【例3.18】 下面程序段可实现段间直接转移。

C1      SEGMENT
        …
        JMP FAR PTR ADDR1
        …
        C1 ENDS
        C2 SEGMENT
        …
ADDR1: MOV CL,AL
        …
C2      ENDS

图3.20 例3.18指令的执行过程及转移地址的形成

程序中,由段定义语句SEGMENT/ENDS(第4章介绍)定义了两个代码段C1段和C2段,JMP指令在C1段中,而要转移去的目的标号ADDR1却在C2段中。指令的执行过程及转移地址的形成如图3.20所示。

直接转移同样需要在指令中明确给出要跳转到的目的标号。

⑤ 段间间接转移

格式:JMP DWORD PTR OPR

操作:IP←(EA)

   CS←(EA+2)

其中,OPR只能是存储器操作数。

指令中由操作数OPR的寻址方式确定一个有效地址EA,指向存放转移地址的偏移地址和段基址的单元,根据寻址方式求出EA后,访问相邻的4个字节单元,低位字单元的16位数据送到IP寄存器中,高位字单元中的16位数据送到CS寄存器中,从而找到要转移到的目标地址,实现段间间接转移的目的。

例如:JMP DWORD PTR ALPHA [BP][DI]

也可以写成 JMP DWORD PTR [BP+DI+ALPHA]

存放转移地址(包括段基址和偏移地址)的堆栈段中的存储单元的物理地址为SS×16+BP+DI+ALPHA。执行JMP指令时,从堆栈段中物理地址确定的连续4个单元中取出两个字数据,将低位字送IP,高位字送CS。此时,程序便无条件转移到新的CS确定的代码段和新的IP为偏移地址的地方继续执行程序。

(2)CALL(call a procedure):过程调用指令

格式:CALL 过程名

操作:CPU暂停执行下一条指令,无条件调用指定的过程。

“过程”即“子程序”。为了便于模块化程序设计,往往把程序中某些具有独立功能的部分编写成独立的程序模块,称为子程序。在程序中,可用调用指令CALL来调用这些子程序,而在子程序执行完后又用返回指令RET返回主程序继续执行。

与JMP指令相似,CALL指令也有4种基本格式。

① 段内直接调用

格式:CALL NEAR PTR 过程名(或CALL 过程名)

操作:SP←SP-2

   (SP+1),(SP)←IP

   IP←IP+D16

将子程序的返回地址(CALL指令的下一条指令的首地址)存入堆栈中,使CPU无条件地转到子程序的入口地址去执行,该入口地址是当前IP的值与16位偏移量D16相加的和(或称调用以过程名为首地址的子程序)。

由于是段内调用,调用程序和子程序同在一个代码段中,因此,不论是需要保护的返回地址,还是要调用的子程序首地址,只需要16位的偏移地址,即只改变IP的值,不改变CS值。直接调用是指在CALL指令中明确指出要调用程序的首地址(或称过程名)。

② 段内间接调用

格式:CALL WORD PTR DST

操作:SP←SP-2

   (SP+1),(SP)←IP

   IP←(EA)

段内间接调用指令执行的操作步骤与段内直接调用大致相同,主要区别是子程序入口地址DST的寻址方式不同而已。指令的DST可以是寄存器操作数或存储器操作数,不允许采用立即数和段寄存器操作数。执行CALL指令时,CPU把DST所对应的寄存器的内容或存储器有效地址所在字单元的内容送入IP寄存器,作为子程序的入口地址,由于还是段内调用,因此CS值保持不变。

例如:CALL DI

   CALL WORD PTR [BX]

第一条指令,寄存器DI的内容就是子程序的入口地址。第二条指令,将BX的值确定的相邻两个存储单元的内容作为子程序入口地址去修改IP,实现段内间接转移。

③ 段间直接调用

格式:CALL FAR PTR 过程名 (或CALL 过程名)

操作:SP←SP-2

   (SP+1),(SP)←CS

   SP←SP-2

   (SP+1),(SP)←IP

   IP←子程序入口地址的偏移地址

   CS←子程序入口地址的段基址

其中,过程名即为段间直接调用的子程序的入口地址。段间直接调用指令的操作分为两步:第一步,保护返回地址,即将CALL指令的下一条指令的地址(包括CS和IP的值)先后压入堆栈保护,其顺序为先CS后IP;第二步,将子程序入口地址(也包括段基址和偏移地址)分别送入IP和CS,从而实现段间直接调用。

由于是段间调用,调用程序和子程序不在同一代码段中,因此,不论是需要保护的返回地址,还是要调用的子程序首地址,均包括段基址和偏移地址。直接调用同样是指在指令中明确给出要调用的过程名。

④ 段间间接调用

格式:CALL DWORD PTR DST

操作:SP←SP-2

   (SP+1),(SP)←CS

   SP←SP-2

   (SP+1),(SP)←IP

   IP←(EA)

   CS←(EA+2)

其中,DST只能是各种寻址方式确定的存储器操作数。执行的步骤分为两步:第一步,将返回地址(包括IP和CS值)压入堆栈保护;第二步,将指令的寻址方式确定的有效地址EA和EA+1两个字节单元的内容送入IP,将EA+2和EA+3两个字节单元的内容送入CS。这样既修改了IP,又修改了CS,将CPU引导到另一个代码段内子程序的首地址去执行。

例如:CALL DWORD PTR [BX]

   CALL DWORD PTR [BP][SI]

(3)RET(return from a procedure):过程返回指令

过程返回指令一般设置在子程序的末尾。它的功能是,从堆栈中弹出由CALL指令压入的返回地址值,迫使CPU返回到主程序中CALL指令的下一条指令去继续执行。段内返回指令把堆栈弹出的两个字节内容送IP寄存器,而段间返回指令则将堆栈弹出4个字节的内容分别送IP和CS。

① 段内返回

格式:RET

操作:IP←(SP+1),(SP)

   SP←SP+2

② 段内带立即数返回

格式:RET EXP

操作:IP←(SP+1),(SP)

   SP←SP+2

   SP←SP+D16

其中,EXP是一个表达式,根据它的值可计算出位移量D16。这种指令允许返回地址出栈后修改堆栈指针,这就便于调用程序在使用CALL指令调用子程序以前,把子程序所需要的参数入栈,以便子程序运行时使用这些参数。当子程序返回后,这些参数不再有用,就可以修改指针使其指向参数入栈以前的值,即自动删除原子程序参数所占用的字节。

③ 段间返回

格式:RET

操作:IP←(SP+1),(SP)

   SP←SP+2

   CS←(SP+1),(SP)

   SP←SP+2

④ 段间带立即数返回

格式:RET EXP

操作:IP←(SP+1),(SP)

   SP←SP+2

   CS←(SP+1),(SP)

   SP←SP+2

   SP←SP+D16

这里EXP的含义与段内带立即数返回指令相同。

2.条件转移指令

条件转移指令根据对标志寄存器状态位的测试结果来决定程序的走向,当条件满足时,控制程序转移到目标标号指定的那个存储单元去执行程序,否则,程序不发生转移,依然顺序向下执行。

所有条件转移指令的寻址方式只有一种,即位移量为8位的相对寻址方式,因此都是短程转移,即转向语句的目标地址必须在当前代码段内,相对位移只能在-128~+127字节范围内。

条件转移指令共有18条,分为三类:第一类根据两个无符号数比较/相减的结果决定是否转移;第二类根据有符号数的比较/相减结果决定是否转移;第三类根据单个标志位的值来决定程序是否转移。条件转移指令的指令名称、指令格式及测试条件见表3.7。

表3.7 条件转移指令

【例3.20】 在存储器中有一个首地址为ARRAY的N字数组,要求测试其中正数、0及负数的个数。正数的个数放在DI中,0的个数放在SI中,并根据N-DI-SI求得负数的个数放在AX中。

程序如下:

            MOV CX,N                 ; 循环次数计数器CX置初始值
            MOV BX,0                 ; BX清0,设置数据指针
            MOV DI,0                 ; DI清0,累计正数个数
            MOV SI,0                 ; SI清0,累计0的个数
AGAIN:     CMP WORD PTR ARRAY[BX],0 ; 取一个数组元素与0进行比较
            JLE LESS OR EQ            ; 元素≤0,转到LESS-OR-EQ标号
            INC DI                    ; 元素>0,使DI+1,累计正数个数
            JMP SHORT NEXT
LESS-OR-EQ:JL NEXT                   ; 元素<0,转到NEXT标号
            INC SI                    ; 元素=0,使SI+1,累计0的个数
NEXT:      ADD BX,2                 ; 修改数据指针,指向下一个元素
            DEC CX                    ; 循环计数器减1
            JNZ AGAIN                 ; N个元素未测试完,转到AGAIN标号
            MOV AX,N                 ; 数组已测试完,进行N-DI-SI运算
            SUB AX,DI
            SUB AX,SI                ; 求得负数的个数存入AX中

3.循环控制指令

循环控制指令又称迭代控制指令,用来管理程序循环的次数。它与一般的条件转移指令相同之处是:要依据给定的条件是否满足来决定程序的走向。当满足条件时,发生程序转移;若不满足条件,则顺序向下执行程序。它与条件转移指令不同之处是:循环指令要对CX寄存器的内容进行测试,用CX的内容是否为0作为转移条件,或把CX的内容是否为0与ZF标志位的状态相结合作为转移条件。所有循环控制指令程序转移的范围只能在-128~+127字节内,具有短距离(SHORT)属性。循环控制指令的指令名称、指令格式及测试条件见表3.8。

表3.8 循环指令

由表3.8可知,JCXZ指令执行时不影响CX的内容;而执行其他的循环控制指令时,都会先使CX寄存器的内容自动减1,然后再判断CX的内容是否为0,当CX≠0时才可能转移。

【例3.21】 为检查当前数据段所在的64K内存单元能否正确地进行读/写操作,一般做法是:先向每个字节单元写入一个位组合模式01010101B(55H)或10101010B(AAH),然后读出来进行比较,若读/写正确,则转入处理正确的程序段,否则转入出错处理程序段。

程序如下:

       XOR   CX,CX        ; 初始值清0,CX=0
       XOR   BX,BX        ; BX寄存器清0,BX=0
       MOV   AL,01010101B ; 设置位组合模式,AL=01010101B=55H
CHECK:MOV   [BX],AL      ; 将55H写入存储单元
       INC   BX            ; 修改地址,BX←BX+1
       CMP   [BX-1],AL    ; 取出写入单元的内容与AL(55H)相比较
       LOOPZ CHECK         ; 满足ZF=1,且CX≠0则转到CHECK执行
       JCXZ  RIGHT         ; CX=0,64KB内存单元均能正确读/写,转到RIGHT执行
ERROR:…                  ; 一旦不能正确读/写,转入出错处理程序
       …
RIGHT:…                  ;处理正确程序
       …

4.中断指令

中断指令共有三条,见表3.9。

表3.9 中断指令

(1)INT(interrupt):中断指令

格式:INT TYPE

操作:SP←SP-2

   (SP+1),(SP)←FR

   SP←SP-2

   (SP+1),(SP)←CS

   SP←SP-2

   (SP+1),(SP)←IP

   IP←(TYPE×4)

   CS←(TYPE×4+2)

其中,TYPE为中断类型号,可以是常数或常数表达式,其值必须在0~255范围内。

(2)INTO(interrupt on overflow):溢出中断指令

格式:INTO

操作:若OF=1,则

   SP←SP-2

   (SP+1),(SP)←FR

   SP←SP-2

   (SP+1),(SP)←CS

   SP←SP-2

   (SP+1),(SP)←IP

   IP←(0010H)

   CS←(0012H)

当OF=0时,则溢出中断指令执行空操作。

(3)IRET(interrupt return):中断返回指令

格式:IRET

操作:IP←(SP+1),(SP)

   SP←SP+2

   CS←(SP+1),(SP)

   SP←SP+2

   FR←(SP+1),(SP)

   SP←SP+2

3.3.6 处理器控制类指令

处理器控制类指令只能完成对CPU的简单控制功能,共有12条指令,见表3.10。

表3.10 处理器控制类指令

注:·表示运算结果不影响标志位。

1.对标志位进行操作的指令

(1)对CF标志位进行操作的指令

(2)对DF标志位进行操作的指令

(3)对IF标志位进行操作的指令

2.同步控制指令

同步控制指令有三条,它们的操作均不影响标志位。

(1)WAIT(puts processor in wait state):等待指令

WAIT指令可使处理器处于空转操作状态,也可用来等待外部中断发生,但中断结束后仍返回WAIT指令继续等待。

(2)ESC(escape):外部操作码,源操作数交权指令

其中,外部操作码是一个由程序员规定的6位立即数,源操作数为存储器操作数。这条指令主要用于与协处理器配合工作。当CPU读取ESC指令后,利用6位外部操作码来控制协处理器,使它完成某种指定的操作,而协处理器则可以从CPU的程序中取得一条指令或一个存储器操作数。这相当于在CPU执行ESC指令时,取出源操作数交给协处理器。

(3)LOCK(lock system bus prefix):封锁总线指令

LOCK不是一条独立的指令,常作为指令的前缀位于任何指令的前端。凡带有LOCK前缀的指令,在该指令执行过程中,都禁止其他协处理器占用总线,故将它称为总线锁定前缀。

3.其他控制指令

其他控制指令,它们的操作不影响标志位。

(1)HLT(halt):暂停指令

HLT指令迫使CPU暂停执行程序,只有当下面三种情况之一发生时,CPU才退出暂停状态:① 在CPU的复位输入端RESET线上有有效的复位信号;② 非屏蔽中断请求NMI端出现请求信号;③ 可屏蔽中断输入端INTR线上出现请求信号,且中断允许标志位IF=1,CPU允许中断。

(2)NOP(no operation):空操作指令

NOP指令并不使CPU完成任何有效操作,只是每执行一次该指令需要占用三个时钟周期的时间,常用于延时或在调试时用于取代其他指令。