1、第第7章章 编程中的高级处理技术编程中的高级处理技术 7.1 移位指令与应用移位指令与应用 7.2 串操作串操作 7.3 宏宏*7.4 重复汇编重复汇编 本章要点本章要点 习题七习题七 返回章目录7.1 移位指令与应用移位指令与应用 7.1.1 逻辑左移逻辑左移 【指令格式】SHL d1,d2 【功能】把操作数d1的各个二进制位依次向左移动d2位,移动造成右边的空位填0,结果放回操作d1中。【说明】(1)操作数d1必须是通用寄存器或内存型寻址方式,必须有确定的类型,可以是字节型,也可以是字型,并且对内存型寻址方式可以使用段跨越。(2)操作数d2表示移动的位数,只能是立即数1或者寄存器CL,当移
2、动位数超过1位时,必须把移动位数放在CL中,以CL作为d2操作数。(3)操作数d1各位移动的情况如图7.1所示,从最高位起,将有d2位移到操作数d1之外,除了最后移出的一位放到标志位CF中之外,其余各位均被丢弃,移动造成右边的空位用0填充。位号:15/7 14/6 1 00 CF操作数d1 (4)移动后d1中的数据是在其原值的后面加了d2个0,即把原数值扩大了倍,当扩大后的值超过表示范围时,超过d1类型(字节或字)规定位数的高位部分会自动丢失。(5)该指令对其余标志位的影响情况是:移动后的结果为0则ZF置1,否则ZF清0;移动后d1最高位的值会复制到SF上;如果移动前后d1的最高位不同则OF置
3、1,否则OF清0。移位指令涉及数值计算问题,但并不复杂。比如AX的值是8D56H,CL的值是3,则下面几条指令是SHL指令的正确用法,后面也给出了移位后的结果:SHLAL,1 移位后,AL=0ACH,CF=0,AH不变SHLAX,1 移位后,AX=1AACH,CF=1SHLAH,CL 移位后,AH=68H,CF=0,AL不变SHLAX,CL 移位后,AX=6AD0H,CF=0 7.1.2 算术左移算术左移 【指令格式】SAL d1,d2 【说明】这是一条与SHL完全相同的指令,该指令与SHL指令是同一条机器指令的两种不同写法。7.1.3 逻辑右移逻辑右移 【指令格式】SHR d1,d2 【功能
4、】把操作数d1的各个二进制位依次向右移动d2位,移动造成左边的空位填0,结果放回操作数d1中。【说明】(1)对两个操作数的语法限制以及对标志位的设置情况都与SHL指令相同,见SHL指令说明的(1)、(2)和(5)。(2)SHR指令在位的移动方向上与SHL指令刚好相反,其它方面则很类似,向右移出的最后一位放到CF中,右移造成左边的空位以0填充,如图7.2所示。(3)移动后的值是把d1中的原值作为无符号数,除以的商。位号:15/7 14/6 1 0操作数d1 CF图7.2 逻辑右移指令SHR的功能 7.1.4 算术右移算术右移 【指令格式】SAR d1,d2 【功能】把操作数d1的各个二进制位依次
5、向右移动d2位,移动造成左边的空位填d1原值的最高位,结果放回操作d1中。【说明】SAR指令的功能基本上与SHR的一样,仅仅是移动造成的空位填充方式不同,如果操作数d1移动前最高位是1,则移动造成的空位以1填充,否则以0填充,如图7.3所示。SAR指令执行的结果是把d1中的原值作为带符号数,除以并把除法的商放回d1中。位号:15/7 14/6 1 0 操作数d1 CF 图7.3 算术右移指令SAR的功能 7.1.5 循环左移循环左移 【指令格式】ROL d1,d2 【功能】把操作数d1的各个二进制位向左移动d2位,从d1左端移出的每一位再依次移到右端空出的位上,最后移出的一位还要送到CF中。可
6、以把d1的各位看作是首尾相接的一个环状,如图7.4所示,把各位的值按逆时针方向旋转d2格,再从环原先的连接部断开,可以得到ROL指令执行后的结果。14/615/7CF01图7.4 循环左移指令ROL的功能 7.1.6 循环右移循环右移 【指令格式】ROR d1,d2 【功能】把操作数d1的各个二进制位向右移动d2位,从右端移出的各位再依次移到d1右端空出的位上,最后移出的一位还要送到CF中。ROR是循环右移指令,与ROL指令相比,只是移位的方向不同,把图7.4中的移动方向改为顺时针方向,从第0位上最后一次移出的位送到CF中,就是ROR指令的功能。7.1.7 带进位的循环左移带进位的循环左移 【
7、指令格式】RCL d1,d2 【功能】把操作数d1的各位与CF联合在一起,构成9个或者17个二进制位,向左移动d2位,从左端移出的各位再依次移到右端空出的位上。从功能上说,可以把d1的各位与CF一起,看作首尾相接的一个环状,如图7.5所示,把各位按逆时针方向旋转d2格,再从环原先的连接部断开,可以得到ROL指令执行后的结果,包括CF的设置情况。14/615/7CF01 图7.5 带有CF的循环左移指令RCL的功能 7.1.8 带进位的循环右移带进位的循环右移 【指令格式】RCR d1,d2 【功能】把操作数d1的各位与CF联合在一起,构成9个或者17个二进制位,向右移动d2位,从右端移出的各位
8、再依次移到左端空出的位上。带进位CF的循环右移指令的功能可以参照图7.5,把移动方向改为顺时针即可。逻辑移位与算术移位指令除了功能本身描述的二进制位的移动之外,还用于把一个字节型或字型数据乘以/除以2n。乘除法指令是所有8088指令中最耗时的,所花费的时间是加减法的2030倍,是移位指令的3550倍,因此如果能用移位指令和加减法指令代替乘除法指令,将大大提高程序的执行速度。【例7.1】编写程序段,把AX中的无符号数乘以8,如果有溢出,忽略超过16位的部分。【解】乘以8的操作可以通过在二进制数的后面加3个0完成,即左移3位,移出部分自动丢失。只需要在CL中放移动位数(3位),再用SHL指令移位即
9、可:MOV CL,3 SHL AX,CL 对于双字型数据,或者位数更多的复杂数据,也可以用移位指令与逻辑运算指令配合,简化乘除法的运算。【例7.2】编写程序段分别完成下列计算。(1)把(DX,AX)构成的无符号双字除以4,商放在(DX,AX)中,余数放BX中。(2)把(DX,BX)构成的无符号数乘以17,结果仍放在(DX,BX)中,忽略溢出。【解】(1)二进制无符号数除以4,就是把它向右移动两位,高位补0,原二进制数的最低两位就是余数。MOV BX,AX AND BX,3;取被除数的最低两位,作为余数 SHR DX,1;右移一位,移出位放到CF中 RCR AX,1;右移一位,最高位以CF的值,
10、即DX 的移出位填充 SHR DX,1 RCR AX,1 (2)17可以看作(16+1),因此可以把BX先左移4位,结果记在(DX,BX)中,再与原数据相加。MOV SI,BX;保存BX的原值MOV AX,BX;保存BX的原值MOV DI,DX;保存DX的原值MOV CL,4SHL BX,CL;低字BX左移4位SHL DX,CL;高字DX左移4位MOV CL,12SHR AX,CL;取原数据的低字的左4位,放在AX的 右4位上OR DX,AX;把AX中存放的数据加到DX中ADD BX,SIADC DX,DI 在例7.2的第(1)小题中,由于没有直接的指令把双字型数据移位,所以通过CF作为过渡,
11、连续做两次字型数据的移位,先把高字DX右移1位,移出位暂时放在CF中,再用带进位CF的循环右移指令,在把低字移位的同时,把暂存在CF中的那1位移到低字的最高位上。第(2)小题中移动的位数较多,如果用循环的方式实现就违背了提高处理速度的本意。程序段中用AX取出低字部分的值,右移12位,把应该从低字移到高字的4位放在了AX的最低4位上。再把高字DX用SHL指令左移4位,移出部分自动丢失,右4位补0,最后把AX中存放的4位数据用OR指令(也可以用ADD指令)放到DX的低4位上,从而实现双字(DX,BX)乘以16的操作。7.2 串操作串操作 汇编语言中的“串”是指内存中连续存放的若干个字节型或字型数据
12、构成的一个整体,相当于一个数组。8088为这种“数组”的操作提供了专门的串操作指令,这些指令与循环或附加在串指令上的前缀配合,可以依次对串中的数据进行处理。串操作指令都要求先把数组首元素(或者最后一个元素)的地址放在指定的变址寄存器中,每处理一个数组元素,串指令本身自动把变址寄存器的内容做相应的变化,使其指向下一个待处理的元素。根据实际需要,串操作指令可以按数组存放的内存地址从小到大进行处理,也可以从大到小处理,CPU在完成串指令时,会根据标志寄存器中的DF标志位选择处理的方向。7.2.1 DF标志位标志位 DF是8088中的一个重要的控制标志位,它决定了串指令的处理方向。当CPU执行到一条串
13、操作指令时,如果DF的值是0,CPU会把指令相应的变址寄存器的值增加,按地址由小到大的方向处理;反之如果DF的值是1,CPU会把变址寄存器的值减小,按地址由大到小的方向处理。8088提供有两条专用指令设置DF,分别是CLD和STD指令。【指令格式】CLD 【功能】把标志位DF清0。【指令格式】STD 【功能】把标志位DF置1。7.2.2 串操作指令串操作指令 8088指令系统中共设计有5条串操作指令,分别用于完成从串中取出数据、往串中存入数据、串复制、串比较等操作。7.2.2.1 LODS指令指令从串中取出数据从串中取出数据 按照串中存放的是字节型数据还是字型数据,有两条指令分别用于从串中取出
14、一个元素。【指令格式】LODSB 或 LODSW 【功能】(1)LODSB进行字节型串操作,从内存中DS:SI所确定的逻辑地址处取出一个字节的数据,送到AL中。当DF0时,令SISI1,当DF1时,令SISI1。(2)LODSW进行字型串操作,从内存中DS:SI所确定的逻辑地址处取出一个字型数据,送到AX中。当DF0时,令SISI2,当DF1时,令SISI2。本书中把LODSB指令和LODSW指令统称作LODS指令,以下各串操作指令也做类似处理。LODS指令要求把串放在DS所指向的段中,SI则存放将要处理的元素的偏移地址。对字节型的串,每个元素占1字节,所以执行一次LODSB指令,SI中的值会
15、根据DF的情况自动加1或减1;而字型的串中每个元素占2字节,SI需要加2或减2后才能指向下一个元素。串指令LODS实际上是把一条MOV指令和一条ADD(或SUB、INC、DEC等)指令综合在一起,可以说,没有串指令同样可以编写数组操作的程序,但串指令会使这种操作简化。【例7.3】设DS段中的变量arr中存放了一个带符号的字型数组,元素个数已放在字型变量arrlen中(0)。编写程序段,利用串操作指令,统计出该数组中正数、0和负数各多少个,结果分别放在DS段中的字型变量countp、count0和countn中。【解】MOV CX,arrlenMOV countp,0MOV count0,0MO
16、V countn,0LEA SI,arr ;DS已有正确值,只要把SI 指向串首地址 CLD ;清方向标志lab1:LODSWCMP AX,0JG lab2;大于0转JL lab3;小于0转INC count0JMP lab4lab2:INC countpJMP lab4lab3:INC countnlab4:LOOP lab1 7.2.2.2 STOS指令指令往串中存入数据往串中存入数据 【指令格式】STOSB 或 STOSW 【功能】(1)STOSB进行字节型串操作,把AL的值送往内存中由ES:DI所确定的内存中。当DF0时,令DIDI1,当DF1时,令DIDI1。(2)STOSW进行字型
17、串操作,把AX的值送往内存中由ES:DI所确定的内存中。当DF0时,令DIDI2,当DF1时,令DIDI2。STOS指令主要用于把一段连续的存储区域以AL或AX中的值填充,特别的是,存储区的段地址必须放在附加段寄存器ES中。STOS与LODS指令配合,还可以从一个串中取出数据,有选择地存到另一个串中。【例7.4】设DS段中的变量arr1中存放了一个带符号的字型数组,元素个数已放在字型变量arr1len中(0)。编写程序段,试利用串操作指令,把该数组中非0元素复制到DS段中的另一个字型变量arr2中,要求在arr2中连续存放,并统计出非0元素的个数填在变量arr2len中。【分析】首先把DS、S
18、I、ES和DI指向正确的位置,然后利用循环指令,每次从arr1中取出一个数,若不是0,则存往arr2。由于是字型数据,循环结束后DI的值减去arr2的偏移地址可得到保存下来的数据占据了多少字节,除以2后即得元素个数。【解】【解】PUSH DSPOP ES;令ESDSLEA SI,arr1LEA DI,arr2MOV CX,arr1lenCLD;准备好取出数据的串和存入数据的串的首地址lab1:LODSWTEST AX,AXJZ lab2;AX为0转STOSWlab2:LOOP lab1SUB DI,OFFSET arr2SHR DI,1;除以2MOV arr2len,DI 7.2.2.3 MO
19、VS指令指令串复制串复制 【指令格式】MOVSB 或 MOVSW 【功能】(1)MOVSB进行字节型串复制,把DS:SI所指向的一个字节型数据送往ES:DI所指向的内存中。当DF0时,令SISI1,DIDI1;当DF1时,令SISI1,DIDI1。(2)MOVSW进行字型串复制,把DS:SI所指向的一个字型数据送往ES:DI所指向的内存中。当DF0时,令SISI2,DIDI2;当DF1时,令SISI2,DIDI2。MOVS指令可以实现把内存中的一个数据,不经过寄存器的过渡由一处复制到另一处。这一点是MOV指令做不到的。MOVS指令与循环控制指令配合,可以完成数据块的复制。被复制的数据串称为源串
20、,复制到的目的地称为目标串。如果源串与目标串所占据的内存是完全分离的,数据传递可以按由串首至串尾的次序进行,也可以按相反的方向进行。但是,当两者占据的内存区域有部分重叠时,需要注意用DF控制方向,当源串首地址小于目标串首地址时,应由尾至首进行传送,源串首址大于目标串首址时,则由首至尾传送。【例7.5】设字节型变量str中存放了100个字符,编写程序段完成下列操作:(1)删除串中前5个字符,并把后续字符前移。(2)把串中各字符向后移一个字节,在串首插入一个空格符。【分析】第(1)题要把串的后95个字节向前移动,是源串首址大于目标串首址的情况,需要自首至尾进行移动;第(2)题正相反,源串首址小于目
21、标串首址,只能按由尾至首的方向移动。【解】(1)MOV AX,SEG str;取变量str所在的段地址MOV DS,AXMOV ES,AXLEA SI,str+5;取源串首偏移地址LEA DI,str;取目标串首偏移地址MOV CX,95;置复制字节数CLDlab:MOVSB;字节型复制LOOP lab(2)MOV AX,SEG strMOV DS,AXMOV ES,AXLEA SI,str+99;取源串尾的偏移地址LEA DI,str+100;取目标串尾的偏移地址MOV CX,100;复制100个字节STDlab:MOVSBLOOP labMOV str,7.2.2.4 CMPS指令指令串比
22、较串比较 【指令格式】CMPSB 或 CMPSW 【功能】(1)CMPSB进行字节型串比较,把DS:SI所指向的一个字节型数据与ES:DI所指向的一字节相减,把相减结果反映到条件标志位上。当DF0时,令SISI1,DIDI1;当DF1时,令SISI1,DIDI1。(2)CMPSW进行字型串比较,把DS:SI所指向的一个字型数据与ES:DI所指向的一个字相减,相减结果反映到条件标志位上。当DF0时,令SISI2,DIDI2;当DF1时,令SISI2,DIDI2。程序设计中经常会遇到比较问题,比较两个符号串是否完全相同,或者比较两个串按字典顺序的大小,这一类问题正是CMPS指令发挥作用的地方。【例
23、7.6】编写子程序,按字典排序法,比较两个已知长度的字符串的大小。【解】;入口参数:DS:SI和ES:DI分别存放第1个串和第2个串的起始逻辑地址 ;CX和DX分别放两个串的串长 ;出口参数:AL为1表示第1个串大,AL为-1表示第2个串大,AL为0表示两者相等 ;破坏寄存器:AH,CX,SI,DIstrcmp PROC NEAR CLD MOV AH,0 ;记载串长1串长2 CMP CX,DX JB lab1 MOV AH,1 ;记载串长相等 JE lab1 MOV CX,DX ;按第2个串的长度进行比较 MOV AH,2 ;记载串长1串长2 lab1:JCXZ lab2 CMPSB JA
24、lab3 ;串1串2转 JB lab4 ;串1串2转 LOOP lab1 lab2:CMP AH,1 JB lab4;串1串2转 JA lab3;串1串2转 MOV AL,0 JMP lab5 lab3:MOV AL,1 JMP lab5 lab4:MOV AL,-1 lab5:RET strcmp ENDP 7.2.2.5 SCAS指令指令串扫描串扫描 【指令格式】SCASB 或 SCASW 【功能】(1)SCASB把AL与字节型串中数据比较,用AL减去ES:DI所指向的一个字节型数据,相减结果反映到条件标志位上。当DF0时,令DIDI1;当DF1时,令DIDI1。(2)SCASW把AX与字
25、型串中数据比较,用AX减去ES:DI所指向的一个字,结果反映到条件标志位上。当DF0时,令DIDI2;当DF1时,令DIDI2。SCAS指令通常用于查找一个数组中是否存在某个指定的值。该指令不改变数组中的任何数据,也不改变AX或AL的值,可以用循环控制的方法连续查找。【例7.7】编写子程序,查找一个字型数组中是否存在一个给定的值。【解】;入口参数:ES:DI存放字型数组的首地址,CX中放串中元素个数 ;AX放指定查找的值 ;出口参数:CF为1表示找到,CF为0表示没找到 ;破坏寄存器:CX,DIsearch PROC NEAR JCXZ lab0 CLD lab1:SCASW JE lab2
26、LOOP lab1 lab0:CLC JMP lab3 lab2:STC lab3:RET search ENDP 7.2.3 串重复前缀串重复前缀 串操作指令是对内存中连续存放的一批数据进行处理的一种高效、快捷的方法,它往往需要循环控制指令的配合。对于那些单纯是数据块复制、查找、比较的操作,汇编语言中还设计有3个串操作重复前缀,以进一步提高编程和数据处理的效率。串操作前缀是附加在串操作前面的指令,它是一种以CX为计数器的重复操作指示器,用以简化循环操作控制。使用串操作前缀的一般格式是:串前缀 串操作指令 7.2.3.1 REP前缀前缀 【功能】当CX的值不是0时,重复执行后面的串操作指令,每
27、执行一次,把CX的值减1,直到CX0为止。图7.6描述了REP串前缀的功能。REP前缀将使它后面的串操作指令重复执行,每执行一次串指令就把CX的值减1,直到CX减到0为止。图7.6描述了带有REP前缀的串指令的执行方式。可以看到,这是一种先判断后重复的循环,如果CX的值是0,则串操作指令一次都不执行,这与LOOP指令控制的循环是不同的。CX=0?CX CX-1 执行一次后面的串指令 YN完成 图7.6 REP前缀的功能 REP前缀通常加在MOVS或STOS串指令的前面,可以用一条指令把一个串复制到内存的另一个地方,或者把一段内存区域用一个特定值填充。REP前缀一般不与另外3条串指令连用。7.2
28、.3.2 REPZ和和REPNZ前缀前缀 REPZ和REPNZ也是串指令前缀,与REP一样都是用于控制后面的串指令重复执行,但重复执行不仅依赖于CX的值,还依赖于标志寄存器中的ZF标志位。【功能】带有REPZ前缀的串指令按下列方式执行:(1)若CX0,则结束指令的执行,否则转(2)。(2)CX CX1。(3)执行一次串指令。(4)若ZF0,则结束指令的执行,否则转(1)。REPNZ的功能与REPZ仅在第(4)项不同,REPZ是在ZF1时控制串操作重复执行,而REPNZ则是在ZF0时控制串操作重复执行。REPZ和REPNZ的功能可以用图7.7描述。CX=0?CX CX-1 执行一次后面的串指令
29、ZF=1?YNNY完成(a)REPZ串前缀的功能 CX=0?CX CX-1 执行一次后面的串指令 ZF=0?YNNY完成(b)REPNZ串前缀的功能 REPZ和REPNZ前缀通常加在CMPS或SCAS串指令的前面,完成连续比较操作。这两个前缀各自又有一种功能完全相同的变形,REPZ可以写作REPE,REPNZ可以写作REPNE。7.2.3.3 串前缀的应用串前缀的应用 根据串前缀与串指令的功能,什么样的串指令前面配什么样的串前缀是有一定限制的。表7.1列出了串前缀与串指令之间的配合关系,其中的“”表示对应的串指令与串前缀可以配合使用,“”表示不能,“”表示相应的用法没有实用价值。串前缀串指令R
30、EPREPZ(REPE)REPNZ(REPNE)LODSSTOSMOVSCMPSSCAS表7.1 串前缀与串指令之间的配合关系 从表7.1中可以看到,LODS指令一般不与串前缀配合使用,因为LODS是从串中取出数据放到AL或AX中,每取一个数据就应该做适当的处理,然后再去取下一数据,否则后取出的数据将取代AL或AX中的原有数据,使得只有最后一次取出的数据被保留下来。画有“”的部分是指根本不存在这样配合的用法。实际上,8088的指令系统中只有两个机器码与串前缀对应,并且,对于MOVS、STOS和LODS,不论串指令的前面加的是什么前缀,都按REP进行处理,汇编程序在翻译时既不报错也不警告;对CM
31、PS和SCAS指令,如果前面加上了前缀REP,汇编程序将按REPZ进行翻译。串前缀的用途在于代替控制串操作的循环结构,下面的例7.8和例7.9就是这种简化的典型用法。【例7.8】把字型变量v1中存放的50个整数复制到变量v2中,先用LOOP指令编写程序段完成复制操作,再用带前缀的串指令简化。【解】用循环控制方法编写的程序段如下:MOV AX,SEG v1 MOV DS,AX ;准备源串的段地址 MOV SI,OFFSET v1 ;准备源串的起始偏移地址 MOV AX,SEG v2 MOV ES,AX ;准备目标串的段地址 LEA DI,v2 ;准备目标串的起始偏移地址 MOV CX,50lab
32、:MOV AX,SI MOV ES:DI,AX LOOP lab该程序段的最后3行可以用一个带前缀的串指令简化,写作:REP MOVSW 【例7.9】编写一个子程序,判断一个数据串中是否存在一个给定的值。要求子程序对字节型和字型的串都能判断,以CF作为出口参数,如果在串中找到目标值,在CF位置1,否则令CF清0。【解】;入口参数:ES:DI数据串的首地址 ;AX查找目标值,字节型数据串则以AL存放目标值 ;CX串中元素个数 ;CF0表示串中元素是字型,1则表示串中元素是字节型 ;出口参数:CF1表示在串中找到了给定值,0表示没找到 ;破坏寄存器:CX,DI search PROC NEAR J
33、CXZ s2 ;串长为0,串中不存在给定数据,转 CLD ;清方向标志,准备按增量方向查找 JC s3 ;入口参数CF为1转字节型查找 REPNZ SCASW ;字型查找 JZ s1 ;找到转 JMP s2 ;未找到转 s3:REPNZ SCASB ;字节型查找 JZ s1 ;找到转 s2:CLC ;置未找到标记 JMP s4 s1:STC ;置找到标记 s4:RET search ENDP 从图7.7的流程图可以看到,带有REPZ和REPNZ前缀的串指令可以在两种情况下结束串操作,一是已执行到CX为0时,二是当ZF不符合要求时。例7.9中,在带有REPNZ前缀的串扫描指令的后面用条件跳转指令
34、进行判断,此时必须能够分辨出是哪一种情况导致串操作结束。可以想到的指令除了JZ、JNZ之外,还有JCXZ,究竟用哪一个为好呢?如果串指令执行完后CX的值不是0,可以肯定是由于ZF不满足重复条件而导致串操作提前结束的,对例7.9就可以知道找到了目标值。这种情况下JCXZ或JZ、JNZ指令都可使用;反之若CX的值是0,表示串操作已经处理到串的最后一个元素,并且最后一次处理的结果已设置在ZF上,但这时如果用JCXZ指令进行判断,就不能分辨最后一次串操作比较或查找的结果是相等还是不等。总之,在带有REPZ或REPNZ前缀的串指令的后面,必须用JZ、JNZ指令判断比较或查找的情况,而不能用JCXZ指令。
35、7.3 宏宏 程序中使用宏分为定义和调用两个部分。宏定义用来说明哪些指令或伪指令是将在程序中重复出现的程序段;宏调用是用来告诉汇编程序,在翻译前先把宏定义中的程序段复制一遍。可见,宏定义和宏调用都是告诉汇编程序如何处理,属于伪操作。7.3.1 宏定义宏定义 【格式】宏名 MACRO 宏体 ENDM 【说明】(1)“宏名”是一个标识符,一个程序中可以定义多个宏,对每一个宏必须以不同的标识符命名,且宏名不能与变量、标号、段名等标识符同名。(2)汇编语言规定,宏定义的起始标记MACRO前面必须写宏的名字,而结束标记ENDM的前面却不允许写任何内容,写在起止标记之间的部分称为宏体,宏体部分通常是一段程
36、序中需要重复使用的指令序列。(3)宏定义仅仅用来告诉汇编程序,将来宏调用时复制的对象是什么,宏定义中的程序段并不是程序的一部分,也就是说,如果程序中定义了一个宏而没有调用它,汇编程序将忽略宏定义。(4)宏定义可以写在程序的任何地方,但习惯上总是把宏定义写在程序的最前面。宏定义与编辑器中的文字块的定义有相似之处,也有一些差别。两者都需要以特定的方式说明开始和结束的位置。文字块是文件的一部分,而宏体中的程序段必须经过宏调用才能复制到源程序中正确的位置,没有被调用的宏体在汇编程序翻译时会被忽略。7.3.2 宏调用宏调用 定义后的宏名又称为宏指令。经宏定义后,就可以在源程序中调用宏了。宏调用的方式是在
37、源程序中需要复制宏体的地方写宏的名字。宏名单独占一行,当源程序被汇编时,汇编程序将对宏调用进行宏体复制,并取代宏名,这种复制操作称为宏展开。为了与源程序的其它部分相区别,后面的叙述中,对由宏调用而展开后得到的指令,都在前面标以加号“”以示区别。【例7.10】参照汇编程序的处理方法,对下面程序中的宏进行展开。back =4CH dosint EQU 21H dispch MACRO MOV AH,2 MOV DL,*INT 21H ENDM code SEGMENT ASSUME CS:codemain:dispch dispch MOVAH,back INTdosintcode ENDS EN
38、Dmain【解】宏展开后的结果是:MOVAH,2 MOVDL,*INT21H MOVAH,2 MOVDL,*INT21H MOVAH,4CH INT21H 由于宏展开是汇编程序翻译的一个步骤,宏展后的结果并不是源程序,所以展开后不再写出完整的程序格式,只列出有效指令部分。可以看到,汇编程序对宏调用与已定义的常量及符号的引用的处理是很类似的,定义部分在汇编处理结束后就已完成它的作用。汇编程序翻译后得到的机器代码中没有宏、常量等的定义,只是调用或引用部分被代换成宏体或定义的内容。宏与常量定义及符号定义的差别在于,常量及符号定义都必须在一行写完,对常量和符号的引用只能代换指令中的操作数,或者操作数的
39、一部分,宏调用则可以代换一段程序。不仅如此,宏还允许代换的内容有个别地方不同,这是通过带参数的宏实现的。7.3.3 带参数的宏带参数的宏 如果宏只能对完全相同的程序段进行复制,那就没有多大的应用价值了,宏的好处主要体现在每次调用而展开的宏体可以不同。这需要在宏定义时以形式参数指明宏体中的哪些部分可以被不同的实际参数代替,每次调用时在宏名字的后面附带实际参数。完整的宏定义格式是:宏名 MACRO 形式参数表 宏体 ENDM 形式参数可以出现在宏体中的任何位置,可以在操作数中,可以在指令助记符的位置,甚至还可以是其中的一部分。下面的几个例子用来说明参数的各种不同用法。【例7.11】普通用法,形式参
40、数出现在操作数的位置。dch MACRO x MOV AH,2 MOV DL,x INT 21H ENDM 源程序中调用宏dch时,应该在宏名字的后面跟一个实际参数,从例7.11的宏体可以看出,这个实际参数应该是用于屏幕显示的一个ASCII字符。因而,源程序中用下面的写法连续两次调用上述宏,就可以实现回车换行操作。宏展开时,对每一次宏调用,将分别以相应的实际参数代换宏体中的形式参数。dch 13 dch 10 【例7.12】形式参数可以出现在助记符的位置。cc MACRO cmd,lab CMP AX,BX cmd lab ENDM 上述宏在调用时可以用不同的实际参数代换形式参数cmd,使得宏
41、体中的第2行是不同的指令。比如下面是几个调用的例子,请读者自己写出宏展开的结果。cc JG,n1 cc JBE,n2 cc JNZ,n3 【例7.13】宏的形式参数可以作为一个标识符的一部分。这时,必须用符号“&”把形式参数与标识符的其余部分分开。例7.12中的宏定义还可以写成下面的形式:cc1 MACRO cmd,lab CMP AX,BX J&cmd lab ENDM 宏体中第2行的J&cmd就是在一个标识符中(例7.13中表现为指令助记符)含有形式参数,符号“&”用于把形参cmd从标识符中分离出来。如果没有分隔符号“&”,汇编程序将把Jcmd作为一个整体处理,而不知道其中的cmd是形参。
42、对例7.12后面的3个调用,调用例7.13中的宏可以达到同样的效果,相应写法是:cc1 G,n1 cc1 BE,n2 cc1 NZ,n3 【例7.14】形式参数还可以出现在变量定义伪操作的初值表中,甚至是以字符串形式出现的初值。msg MACRO num,pname var&num DB HELLO,&pname ENDM 汇编语言规定,字符串中的形参必须用分隔符“&”从其它部分分离出来。对于下面两个宏调用,msg 1,John msg 2,Henrry宏展开的结果是:var1 DB HELLO,John var2 DB HELLO,Henrry *7.3.4 宏操作中形参与实参的对应关系宏操
43、作中形参与实参的对应关系 由于宏是伪操作,形参与实参的对应方法是由汇编程序决定的,与高级语言中形参与实参的对应方式有很大的不同。汇编语言规定:(1)形参表中的多个参数项之间必须用逗号分隔,但实参表的各个参数项可以用逗号也可以用空格分隔。(2)如果形参的数目与实参的数目相等,则按照形参表与实参表中各参数项的次序一一对应。(3)如果形参数目少于实参数目,多余的实参被忽略,汇编程序不做任何提示。(4)如果形参数目多于实参数目,不足的实参作空串处理,汇编程序也不做提示。(5)如果实参中包含逗号、空格等分隔符作有效符号,必须用尖括号“”括起来,避免混淆。【例7.15】设有宏定义如下:data MACRO
44、 p,q v&p DB q ENDM以及下面的宏调用:x =1 data%x,%x x =x+1 data%x,%x其宏展开结果是:v1 DB 1 v2 DB 2 符号“”的作用体现在宏展开中是用常量标识符x的值(第1次宏调用时是1,第2次宏调用时是2),而不是符号x本身,去替换宏展开时的形参。需要注意的是,宏操作与源程序的其它部分一样,都要经过汇编程序的处理。汇编程序在处理带有宏调用的程序时,是先进行宏展开,再进行语法检查及翻译。宏体在定义时,由于可以带有一些形式参数,在没有进行代换之前很可能是不符合语法规则的,但宏调用并展开后是否符合语法规则,需要由汇编程序来判定。如果展开后的指令或伪指令
45、有错,汇编程序只能指出宏调用有错,并指出宏调用所在行的号码,却无法指出究竟是展开后的哪一行不符合语法。因此,对这一类错误提示,程序员只能自己按规则进行宏展开,并判断展开后的结果是如何出的错。对于MASM汇编程序比较熟悉的人,还可以借助于MASM处理源程序时产生的一个.LST文件(只要在MASM提问“Source listingNUL.LST”时输入一个合适的文件名即可产生清单文件),判断程序中的错误,以及查看宏展开的结果。另外,宏调用的优先级高于其它伪指令和指令,所以如果用伪指令或指令助记符等内部保留字作为宏的名字,则汇编程序会把这样的标识符当作宏进行处理,而使得源程序中无法使用其原有的功能。
46、汇编语言中还提供了一个PURGE伪指令,用于在源程序适当的位置取消某个宏。比如:add MACRO x,y ENDM ADD AX,BX PURGE add ADD AX,BX 这样的源程序中,前一个ADD被当作宏调用处理(汇编语言是不分大小写的),进行宏展开;后一个ADD由于已用PURGE伪指令取消了作为宏的add宏调用,使得该标识符恢复原功能,因此是ADD指令。7.3.5 宏体中的标号宏体中的标号 宏体中出现标号分为两种情况:一是在带有跳转功能的指令中,标号作为跳转的目的地,也就是对标号的引用;另一种是宏体中某条指令的前面出现标号,即标号定义。如果宏体中引用标号,由于汇编语言允许从程序的不
47、同地方用跳转指令跳转到同一位置,所以即使源程序中对这样的宏多次调用,展开后的结果并不违反语法规则。但是需要注意,如果标号的引用出现在条件跳转指令中,由于条件跳转的距离是有限制的,所以源程序中在不同位置调用这样的宏,那些离标号定义处较近的可以不超过范围因而没有语法错误,但那些离标号定义处远的就不保证了。【格式】LOCAL 标号1,标号2,【功能】用于告诉汇编程序,在宏展开时,对宏体中出现的“标号1”、“标号2”等标号定义,代换为特殊的各不相同的标号。汇编程序在遇到用LOCAL说明的标号时,会代以?0000、?0001、?0002、等特殊的标识符,以保证宏体中的标号定义在每次宏展开时各不相同,避免
48、重复定义的情况。【例7.16】设有如下宏定义:sum MACRO a,b LOCAL next MOV CX,a LEA BX,b XOR AX,AX next:ADD AX,BX ADD BX,2 LOOP next ENDM并已知buf1和buf2是已定义的两个变量,展开下面的宏调用:sum 5,buf1 sum 7,buf2【解】宏展开结果是:MOV CX,5 LEA BX,buf1 XOR AX,AX?0000:ADD AX,BX ADD BX,2 LOOP?0000 MOV CX,7 LEA BX,buf2 XOR AX,AX?0001:ADD AX,BX ADD BX,2 LOOP
49、?0001 可以看到,两次调用宏sum,展开后的结果中标号分别是?0000和?0001,是不同的。如果宏定义中没有LOCAL伪操作,展开结果中将在两个地方定义标号next,是重复定义。汇编语言还规定,LOCAL伪操作必须出现在宏体的第1行上,在“宏名 MACRO”与LOCAL伪操作之间不允许有任何内容,包括空行、注释。*7.3.6 宏的嵌套宏的嵌套 类似于在子程序中可以调用另一个子程序,在一个宏体中也允许再调用另一个已定义的宏。对这种宏中套宏的宏调用,汇编程序将逐次展开,直到展开后的结果不再含有宏调用为止。【例7.17】设某程序中已定义了3个字型变量v1、v2、v3,下面是源程序中的一段,试展
50、开最后一行的宏调用。mm1 MACRO x MOV AX,x MUL AX ENDM mm2 MACRO a,b,c mm1 a MOV BX,AX mm1 b ADD AX,BX MOV c,AX ENDM mm2 v1,v2,v3【解】展开mm2后得到:mm1 v1 MOV BX,AX mm1 v2 ADD AX,BX MOV v3,AX其中还含有宏调用,再把两个mm1展开后可得到如下结果:MOV AX,v1 MUL AX MOV BX,AX MOV AX,v2 MUL AX ADD AX,BX MOV v3,AX 7.3.7 宏与子程序的比较宏与子程序的比较 (1)处理的时间不同。宏调用