340 likes | 516 Views
第十三章 高级汇编语言技术及其使用简介. 第三节 宏汇编. 我们曾在第七章中学习了一种很重要的程序设计技术 —— 子程序设计。对于程序中经常要使用的独立功能的程序段,我们都可将它设计成子程序的形式,供需要时调用。使用子程序结构具有很多优点:可以节省存储空间及程序设计所花的时间,可提供模块化程序设计的条件,便于程序的调试及修改等。
E N D
第十三章 • 高级汇编语言技术及其使用简介
第三节 宏汇编 我们曾在第七章中学习了一种很重要的程序设计技术——子程序设计。对于程序中经常要使用的独立功能的程序段,我们都可将它设计成子程序的形式,供需要时调用。使用子程序结构具有很多优点:可以节省存储空间及程序设计所花的时间,可提供模块化程序设计的条件,便于程序的调试及修改等。 但使用子程序也需要付出一些额外的时间和空间上的开销,例如,调用子程序需要传递参数、保护和恢复寄存器的内容,还需要执行CALL和RET指令等。若程序中的重复部分只是一组较简单的语句序列,如果将它们设计成子程序,那么,为调用程序所用的开销就可能要超过这个语句序列,这显然是不合算的。 因此,有时特别在子程序本身较短或者是需要传送的参数较多的情况下,使用宏汇编就更加有利。
3.1 MACRO 宏 宏就是将一程序段给定一个名字,在以后的程序中,就可以使用这个名字来代替这段程序。 一、宏的作用 在源程序中,有的程序段要被多次使用,为了不重复书写这些程序段,可将它们定义成宏。宏像过程一样也具有名字,一旦定义了宏,就可以在源程序中通过它的名字对这个程序段加以引用,从而减少了程序的编写量,使源程序更清晰和易读。此外,为了向宏中传递信息,还可以定义带参数的宏,使宏的功能更强,使用更加灵活。
二、宏定义、宏调用与宏展开 宏指令的使用要经过以下三个步骤: (1)宏定义:使用伪指令MACRO、ENDM将重复的语句序列定义成宏指令,选定好形参,并为该宏指令取一符号名(简称宏指令名); (2)宏调用:在程序中需要的地方通过带实参的宏指令名来调用宏定义; (3)宏扩展:由宏汇编程序用宏定义中的语句序列来代替宏调用中的宏指令名,用实参代替形参。 其中,前两步工作必须由用户自己完成,也是我们必须掌握的内容,而第三步是由宏汇编程序在汇编期间完成的。
1、宏定义 宏定义是通过伪指令 MACRO 和 ENDM 来进行的。其中,MACRO 表示宏定义的开始,ENDM 表示宏定义的结束。其格式是: 宏指令名 MACRO [形参表] ┆ (宏定义体) ENDM 其中宏指令名给出该宏定义的名称(其必须唯一),它代表着宏定义所定义的宏定义体的内容,在其后面的源程序中,可通过该名字来调用宏。再者,宏指令名还可以与源程序中的其它的变量、标号或指令助记符相同。也就是说,宏指令具有比指令和伪指令更高的优先权。形参表给出了宏定义中所用到的形式参数,每个形参之间用逗号隔开,它是可选项,形参可以出现在宏定义体中的任何地方。当调用宏时,要用对应的实际参数去取代,以实现向宏中传递信息。宏定义体可以是汇编语言所允许的任意语句(指令或伪指令)序列,它决定了宏的功能。在宏定义体中还可以定义或调用另一个宏。 宏定义是为汇编程序服务的,它只是告诉汇编程序用一个名字来代替一段指令序列,为宏调用作准备,而它本身并不被汇编。
宏定义举例 例13.3.1: INPUTMACRO MOV AH,l INT 21H ENDM 该例定义了一个名为 INPUT 的宏,它代表宏定义体中语句序列的功能,即从键盘输入一个字符并送到 AL寄存器中。 例13.3.2: SUM MACROX,Y,Z MOV AX,X ADD AX,Y MOV Z,AX ENDM 该例定义的宏 SUM 使用了三个形式参数,其功能是将前两个参数相加,结果送到第三个参数中。宏调用时,要用三个实际参数去取代它们。
2、宏调用 宏一旦被定义,就象为指令系统增加了新的指令一祥,在程序中就可以通过宏指令名对它进行任意次的调用。当源程序中要引用已定义的宏时,只要在程序需要的地方写上所需的宏指令名即可。若有参数的话,则用相应的实际参数去取代定义时的形式参数,这种引用称为宏调用。宏调用的格式是: 宏指令名 [实参表 ] 其中,宏指令名必须是在该调用指令前已定义过的名字,实参表给出了宏调用中所用到的实际参数,它是可选项。每个实参可以是任意的一串字符,如标号、常数和寄存器等等。实参的类型和顺序应与宏定义时的形参一一对应,它必须写在同一行上,每个实参之间用逗号隔开。 注意:在程序中,宏定义必须出现在宏调用之前,也就是说必须先定义后调用。因此,往往把宏定义放在程序中所有段的代码之前,即在程序一开始先列出你的程序中所用的所有宏定义,以便在程序中调用。
宏调用举例 例13.3.3 对前面两个例子定义的宏INPUT和SUM进行调用,以实现输入一个字符等操作。 … CODE SEGMENT … INPUT ; 宏调用 … SUM BX,CX,DX ; 宏调用,用实参BX,CX和DX 分别取代形参 X,Y和Z … CODE ENDS …
3、宏展开 当源程序被汇编时,汇编程序将对每个宏调用进行宏展开。宏展开就是用宏定义体取代源程序中的宏指令名,而且用实参取代宏定义中的形参。在取代时,实参和形参是一一对应的,即第一个实参取代第一个形参,第二个实参取代第二个形参……依此类推。 一般说来,实参的个数应该和形参的个数相等,但汇编程序并不要求它们必须相等。如果一个宏调用的实参个数比宏定义时的形参个数多,则多余的实参被忽略;反之,若实参个数比形参个数少,则多余的形参作“空”处理。 例如,汇编程序将上例(例3)中的宏调用 INPUT 展开成如下形式: 1 MOV AH,l 1 INT21H
宏展开举例 将宏调用 SUM BX,CX,DX 展开成如下形式: 1 MOV AX,BX 1 ADD AX,CX • MOV DX,AX 汇编程序在所展开的指令前加上“1”表示这些指令是由宏展开而得到的(较早的版本用符号“+”表示)。 从上面的例子可以看出:由于宏指令可以带形参,调用时可以用实参取代,这就避免了子程序因参数传送带来的麻烦,使宏汇编的使用增加了灵活性。而且,实参可以是常数、寄存器、存储单元名以及用寻址方式能找到的地址或表达式等。从以后的例子中将可看到,实参还可以是指令的操作码或操作码的一部分等,宏汇编的这一特性是子程序所不及的。
三、宏与子程序的区别 宏和子程序都可用来简化源程序并可使程序对它们多次进行调用,从而使程序结构清晰、简洁,符合结构化程序设计风格,但它们之间也有区别和各自的不足。 宏操作可以直接传递和接收参数,它不需通过堆找来进行,因此比较容易编写。子程序不能直接带有参数,当子程序之间需要传递参数时,要注意参数传递的方式是通过寄存器、存储器还是堆找来进行的,相对宏来说,编写上稍复杂一些。子程序是在程序执行期间由主程序调用的,它只占有它自身大小的一个空间;而宏调用则是在汇编期间展开的,每调用一次就把宏定义体展开一次,因而它占有的存储空间与调用次数有关,调用次数越多则占有的存储空间也就越大。前面已经提到,用宏汇编可以免去执行时间上的额外开销,但如果宏调用次数较多的话,则其空间上的开销也是应该考虑的因素。 因此,读者可以根据具体情况来选择使用方案。一般说来,由于宏汇编可能占用较大的空间,所以代码较长或调用次数比较频繁的功能段往往使用子程序而不用宏汇编;而那些较短的且变元较多的功能段,则使用宏汇编就更为合理。
仅是源程序级的简化:宏调用在汇编时进行程序语句的展开,不需要返回;不减小目标程序,执行速度没有改变仅是源程序级的简化:宏调用在汇编时进行程序语句的展开,不需要返回;不减小目标程序,执行速度没有改变 通过形参、实参结合实现参数传递,简捷直观、灵活多变 宏与子程序的比较 比较 宏 子程序 • 还是目标程序级的简化:子程序调用在执行时由CALL指令转向、RET指令返回;形成的目标代码较短,执行速度减慢 • 需要利用寄存器、存储单元或堆栈等传递参数
宏与子程序具有各自的特点,程序员应该根据具体问题选择使用那种方法宏与子程序具有各自的特点,程序员应该根据具体问题选择使用那种方法 通常,当程序段较短或要求较快执行时,应选用宏;当程序段较长或为减小目标代码时,要选用子程序 宏与子程序的比较结论 比较结论 宏 子程序
3.2 宏操作符 MASM 宏汇编程序提供了表 13-1 中列出的宏操作符,用于宏参数的连接。 表 13-1 宏操作符
格式:& 形式参数 或 形式参数 & 功能:在宏展开时,用对应的实际参数替换以“&”操作符为前缀或后缀的形式参数,替换时移去“&”,并将“&”操作符前面或后面的符号连接在一起而形成一个新的符号。 该操作符主要用来方便地修改某些符号的一部分,如修改指令助记符、操作数或字符串的一部分。 (1)连接替换操作符 &
例如:设有如下宏定义: ERRGEN MACRO X ERROR&X:PUSH BX X&AB: MOV AX,‘X’ ABX: MOV BX,‘&X’ AB&X: JMP ERROR ENDM 当宏调用指令为: ERRGEN A 时,汇编程序将其宏展开成如下形式: 1 ERRORA: PUSH BX 1 AAB: MOV AX,‘X’ 1 ABX: MOV BX,‘A’ 1 ABA: JMP ERROR 在该例的宏定义中,凡是用“&”操作符指出的形式参数 X 都被实际参数 A 所替换;而未用“&”操作符指出的 X ,被看作原符号的一部分而未被替换。
(2)文本操作符< > 格式:< 文本 > 功能:将操作符“< >”中的文本作为一个整体参数进行处理。 该操作符主要用在宏调用和 IRP 伪指令中,使参数表中的值作为一个字符串参数(而不是几个参数)来处理。当一个完整的参数中包含逗号、空格符、分号及 & 等等一些特殊字符时,必须要用该操作符将这样的参数括起来,作为一个单一的参数使用。如果该操作符嵌套有多层,则参数为去掉最外层的一对“< >”操作符所剩的内容。例如: < < > > 参数为 < > <;> 参数为分号“;”,而不是注释符
例:INIT MACRO X IRP Y,<X> ;IRP为重复块伪指令,重复IRP与ENDM伪指令之间的语句 DB Y ENDM ENDM 当宏调用指令为: INIT <1,2,3,4,5> 时,汇编程序认为实际参数只有一个,这就是‘1,2,3,4,5’, 而不是 5 个参数。因此宏展开时,是用‘1,2,3,4,5’这个整体去取代形式参数 X,首先将重复块展开成: 1 IRP Y, <1,2,3,4,5> 1 DB Y 1 ENDM 然后进一步展开成: 2 DB l 2 DB 2 2 DB 3 2 DB 4 2 DB 5 的形式。
(3)字符操作符 ! 格式:! 字符 功能:!操作符告诉宏汇编程序,其后的字符不作操作符使用,而是以字符本身的意义进行处理(如分隔符等)。 例:宏中的参数可以用逗号、空格或制表字符来分隔。当调用前面已定义的宏INIT时,若采用如下宏调用指令: INIT 1,2,3,4,5 则汇编程序将认为它是具有 5 个参数的宏调用。为使这里的逗号成为参数的一部分,使这个宏调用只有一个参数‘1,2,3,4,5’,除可以使用< >操作符外,还可利用 ! 操作符, 即: INIT 1!,2!,3!,4!,5 这与 INIT <1,2,3,4,5> 是等价的。
(4)表达式操作符 % 格式:% 表达式 功能:%操作符告诉宏汇编程序获取表达式的值,而非获取表达式文本本身。这个操作符通常在宏调用中使用,不允许出现在形式参数的前面。 例:SAMPLE MACRO X Y=X-2 MOV AX,%Y ;将 Y 的结果→AX ENDM … SYN1 EQU 100 SYN2 EQU 200 … SAMPLE 12 ;宏调用 SAMPLE %(SYNl+SYN2) ;宏调用 在该例的第一个宏调用中,是将实际参数 12 传递给形式参数 X 。因此,宏体中的 Y值为 10,其宏展开为: 1 MOV AX,10 第二个宏调用中是以 % 操作符后的表达式(SYN1+SYN2)的结果(300)作为实际参数传递给 X 的,因此宏体中的 Y 值为 298,其宏展开为: 1 MOV AX,298
(5)宏注释操作符 ;; 格式:;;文本 功能:表示宏注释,它不被宏展开。 使用双分号的宏注释只能在宏定义中出现。由于它不被宏展开,因此节省存储空间,汇编的速度也较快。 在宏定义中也可以使用单分号的注释,但这种注释在宏展开时可能会出现在列表文件 (.LST)中。一般来说,在宏定义时,除绝对必要的注释使用单分号开头外,其余的均应采用双分号的宏注释。 例:将前面的宏定义 SAMPLE 中的注释改为宏注释: MOV AX,%Y ;;将 Y 的结果→AX
3.3 宏指令的使用 下面我们将通过例子,介绍宏指令的基本使用方法。 (1)宏定义可以无变元。 宏定义: OUTPUT MACRO MOV DL,AL MOV AH,2 INT21H ENDM 宏调用: OUTPUT 宏展开: 1 MOV DL,AL 1 MOV AH,2 1 INT21H
(2)变元可以是操作码。 宏定义: OPC MACRO P1,P2,P3 MOVAX,P1 P2P3 ENDM 宏调用: OPC BX,INC,AX 宏展开: 1 MOV AX,BX 1 INC AX
(3)变元可以是操作码的一部分,但在宏定义体中必须用 & 作为分隔符。 宏定义 : LEAP MACROCOND,LAB J&COND LAB ENDM 宏调用: … LEAP Z,THERE … LEAP NZ,HERE … 宏展开: … 1 JZ THERE … 1 JNZ HERE …
(4)变元可以是操作数的一部分。 宏定义 : F0 MACRO P1 JMP TA&P1 ENDM 宏调用: F0 B1 宏展开 : 1JMP TAB1 在这里,如果宏定义写为 F0 MACRO P1 JMP TAP1 ENDM 则在展开时,汇编程序把TAP1看作是一个独立的标号,并不把TAP1中的Pl作为哑元看待,这样就不能得到预期的结果。
(5)变元是 ASCII 串。 宏定义 : MSGGENMACRO LAB,NUM,XYZ LAB&NUMDB ‘HELLO MR.&XYZ’ ENDM 宏调用 : MSGGEN MSG,1,STEPHEN 宏展开 : 1 MSG1 DB ‘HELLO MR.STEPHEN’
(6)宏定义的变元中可以使用 % 操作符。 宏定义: MSG MACRO COUNT,STRING ;;MSG宏定义 MSG&COUNTDB STRING ENDM ERRMSG MACRO TEXT ;;ERRMSG宏定义 CNTR=CNTR+1 MSG %CNTR,TEXT ;;MSG宏调用(宏调用的嵌套) ENDM 宏调用: … CNTR=0 ERRMSG ‘SYNTAX ERROR’ … ERRMSG ‘INVALID OPERAND’ … 宏展开: … 2 MSG1 DB ‘SYNTAX ERROR’ … 2 MSG2 DB ‘INVALID OPERAND’ …
其中,2 表示它是第二层展开的结果。一般在 LST 清单中,使用隐含的.XALL伪操作,即不产生代码的语句在清单中并不列出。为了能看到宏展开后所有的语句,可以在源程序中增加 .LALL 语句,此时就能看到所有的语句了。实际上,本例的展开可分为如下两层: … CNTR=0 CNTR=CNTR+1 1 MSG %CNTR,‘SYNTAX ERROR’ 2 MSG1 DB ‘SYNTAX ERROR’ … CNTR=CNTR+1 1 MSG %CNTR,‘INVALID OPERAND’ 2 MSG2 DB ‘INVALID OPERAND’ … 在一个宏定义中可以调用另外的宏,我们将之称为宏调用的嵌套。其限制条件是:必须先定义后调用。
(7)宏指令名可以与指令助记符或伪操作名相同(7)宏指令名可以与指令助记符或伪操作名相同 在这种情况下,宏指令的优先级最高,而同名的指令或伪操作就失效了。宏的删除伪指令PURGE可以用来在适当的时候取消宏定义,以便恢复指令或伪操作的原始含义。 宏定义: ADD MACRO OPR1,OPR2,RESULT … ENDM 宏调用: … ADD XX,YY,ZZ PURGE ADD … 在宏调用后,用PURGE伪操作取消宏定义,以便恢复ADD指令的原始含义,在PURGE ADD后面所用的ADD指令,则服从机器指令的定义。宏定义一经删除就不能再对其进行调用,否则汇编时将产生错误。 PURGE伪操作可同时取消多个宏操作,此时各宏指令之间用逗号隔开。
(8)局部标号伪指令LOCAL的使用。 在汇编语言源程序中,要求标号必须是唯一的,否则为重复定义出错。而宏定义体内允许使用标号,如: 宏定义: ABSOL MACRO OPER CMP OPER,0 JGE NEXT NEG OPER NEXT: ENDM 如果程序中多次调用该宏定义,则宏展开后就会出现标号的多重定义,这是不允许的。为此系统提供了LOCAL伪操作,在宏定义中说明标号为局部标号,其格式是: LOCAL 局部标号表其中局部标号表内的各标号之间用逗号隔开。汇编程序对LOCAL伪操作的局部标号表中的每一个局部标号建立唯一的符号(用??0000~??FFFF表示)以代替在展开中存在的每个局部标号。必须注意,LOCAL伪操作只能用在宏定义体内,而且它必须是MACRO伪操作后的第一个语句,在MACRO和LOCAL伪操作之间不允许有注释和分号标志 。
例中的ABSOL宏定义在考虑有多次调用可能性的情况下,应定义为:例中的ABSOL宏定义在考虑有多次调用可能性的情况下,应定义为: ABSOL MACRO OPER LOCAL NEXT CMP OPER,0 JGE NEXT NEG OPER NEXT: ENDM 宏调用: … ABSOL VAR … ABSOL BX … 宏展开: … 1 CMP VAR,0 1 JGE ??0000 1 NEG VAR 1 ??0000: … 1 CMP BX,0 1 JGE ??0001 1 NEG BX 1 ??0001: …
(9)宏定义中允许使用宏调用,其限制条件是:必须先定义后调用(9)宏定义中允许使用宏调用,其限制条件是:必须先定义后调用 宏定义: DIF MACRO X,Y MOV AX,X SUB AX,Y ENDM DIFSQR MACRO OPR1,OPR2,RESULT PUSH DX PUSH AX DIF OPR1,OPR2 IMUL AX MOV RESULT,AX POP AX POP DX ENDM 宏调用: DIFSQR VAR1,VAR2,VAR3
(10)宏定义体内不仅可以使用宏调用,也可以包含宏定义(10)宏定义体内不仅可以使用宏调用,也可以包含宏定义 宏定义: DEFMAC MACRO MACNAM,OPERATORMACNAM MACRO X,Y,Z PUSH AX MOV AX,X OPERATOR AX,Y MOV Z,AX POP AX ENDM ENDM 其中MACNAM是内层的宏定义名,但又是外层宏定义的哑元,所以调用DEFMAC时,就形成一个宏定义。
宏调用: DEFMAC ADDITION,ADD 宏展开: 1 ADDITION MACRO X,Y,Z PUSH AX MOV AX,X ADD AX,Y MOV Z,AX POP AX ENDM 形成加法宏定义ADDITION。同样,宏调用 DEFMAC SUBTRACT,SUB 形成减法的宏定义。当然在形成这些宏定义后,就可以使用宏调用 ADDITION VAR1,VAR2,VAR3 而展开成: 2 PUSH AX 2 MOV AX,VAR1 2 ADD AX,VAR2 2 MOV VAR3,AX 2 POP AX