📄 汇009.txt
字号:
了解的内容:在源程序中运用宏的好处,宏嵌套定义的含义和应用方法。知道各种条件汇编伪指令对生成代码的影响。
掌握的内容:宏和子程序的区别,宏的参数和子程序的参数在处理上差异。宏参数中各种特殊运算符的含义和作用。
熟练掌握的内容:宏定义的一般方法,宏引用及其参数传递方法。宏伪指令Local和重复汇编伪指令——REPT、IRP和IRPC——的作用。
建议学习时间:8小时。
第9章 宏
宏是程序设计的另一个基本概念,它是把一段程序代码用一个特定标识符(即:宏名)来表示。这样,在编写源程序时,程序员就可以直接使用该标识符来代替一段代码的编写,从而减少了重复代码的编写工作,也为减少错误,提高程序的可维护性提供了帮助。宏在高级语言(如:C/C++等)也有广泛的使用。
9.1 宏的定义和引用
通常情况下,宏是用来代表一个具有特定功能的程序段,它只需在源程序中定义一次,但可在源程序中引用多次。只要在编写程序时需要,就可以直接使用它。
9.1.1 宏的定义
在使用宏之前必须先定义宏,定义宏一般格式如下:
宏名 MACRO [形参1, 形参2, ……]
… ;宏的定义体
ENDM
在书写宏定义时,必须遵照下列规定:
、MACRO和ENDM是二个必须成对出现的关键字,它们分别表示宏定义的开始和结束;
、MACRO和ENDM之间的部分是宏的定义体,它是由指令、伪指令或引用其它宏所组成的程序片段,是宏所包含的具体内容;
、“宏名”是由程序员指定的一个合法的标识符,它代表该宏;
、宏名可以与指令助忆符、伪指令名相同。在这种情况下,宏指令优先,而同名的指令或伪指令都失效;
、在ENDM的前面不要再写一次宏名,这与段或子程序定义的结束方式有所不同;
、在宏定义的首部可以列举若干个形式参数,每个参数之间要用逗号分隔。
根据上述规定,我们提倡宏名尽可能不要与指令助忆符、伪指令相名,以免引起不必要的误会。
例9.1 定义一个把16位数据寄存器压栈的宏。
PUSHR MACRO
PUSH AX
PUSH BX
PUSH CX
PUSH DX
ENDM
例9.2 定义二个字存储变量相加的宏。
MADDM MACRO OPRD1, OPRD2
MOV AX, OPRD2
ADD OPRD1, AX
ENDM
上述宏定义虽然能满足题目的要求,但由于在定义体中改变了寄存器AX的值,这就使宏的引用产生了一定的副作用。为了使寄存器AX的使用变得透明,可把该宏定义改成如下形式:
MADDM MACRO OPRD1, OPRD2
PUSH AX
MOV AX, OPRD2
ADD OPRD1, AX
POP AX
ENDM
通过在宏定义的开始和结尾分别增加对所用寄存器的保护和恢复指令,就使得:对该宏的任意引用都不会产生任何副作用。
9.1.2 宏的引用
在源程序中,一旦定义了某宏,那么,在该程序的任何位置都可直接引用该宏,而不必重复编写相应的程序段。引用宏的一般格式如下:
宏名 [实参1, 实参2, ……]
其中:实参的位置要与形参的位置要对应,但实参的个数可以与形参的个数不相等。
、当实参的个数多于形参的个数时,多出的实参被忽略;
、当实参的个数少于形参的个数时,没有实参对应的形参用“空”来对应。但在宏展开时,所得到的指令必须是合法的汇编指令,否则,汇编程序将会给出出错信息。
例如:假设已有字变量w1和w2,并且也有例9.2中的宏MADDM,那么,如果要把w2的内容加到w1中的话,就可以在源程序的代码段中按下列方式来引用该宏(点击它,可显示宏展开后的形式):
MADDM w1, w2 1 MOV AX, w2
1 ADD w1, AX
注意:以上的宏展开形式可以在LST文件中看到,宏展开所得到的语句前面有一个数字(如:1),它表示宏展开的层次。
如果在源程序中按下列方式来引用了宏MADDM,那么,在宏展开时就会得到下列语句:
MADDM w1 1 MOV AX, ;显然,指令“MOV AX”是一条非法指令,汇编程序将会指出该错误。
1 ADD w1, AX
9.1.3 宏的参数传递方式
在引用宏时,参数是通过“实参”替换“形参”的方式来实现传递的。参数的形式灵活多样,参数可以是常数、寄存器、存储单元和表达式,还可以是指令的操作码。
例9.3 定义二个字存储变量相加和相减的宏。
解:
方法1:定义二个宏分别实现存储变量的加操作和减操作
方法2:定义一个宏,把存储变量的加和减操作合并在一起
MADDM MACRO OPRD1, OPRD2
MOV AX, OPRD2
ADD OPRD1, AX
ENDM
MSUBM MACRO OPRD1, OPRD2
MOV AX, OPRD2
SUB OPRD1, AX
ENDM
MOPM MACRO OP, OPRD1, OPRD2
MOV AX, OPRD2
OP OPRD1, AX
ENDM
其中:参数OP是一个对应于操作码的形式参数。
下面是引用宏MOPM的二个例子,它们在宏展开时将会得到下列语句:
MOPM SUB, w1, w2
1 MOV AX, w2
1 SUB w1, AX
…
MOPM ADD, w1, w2
1 MOV AX, w2
1 ADD w1, AX
9.1.4 宏的嵌套定义
宏的嵌套定义有二种方式:宏定义体内引用其它的宏和宏定义体内定义其它的宏。
1、宏定义体内引用其它的宏
在宏的定义体中又引用了其它已定义好的宏,这种宏定义方式在实际的编程过程时常会用到。如果被引用的宏还没定义的话,汇编程序将会显示出错信息。
例如:
ABS MACRO OPRD1, OPRD2
…
MOPM SUB, OPRD1, OPRD2 ;引用前面已定义的宏MOPM
…
ENDM
在定义宏ABS时,引用了前面已定义好的宏MOPM。
2、宏定义体内定义其它的宏
宏的定义体内又定义了其它宏,只有在先引用了外层的宏定义,才能引用内层的宏,这是因为,当外层宏展开后,内层宏的定义才变得有效。
这种定义方式虽然有其好的一面,但它使宏的定义更加隐蔽,削弱了程序的可读性,降低了程序的可维护性,因此,这种宏定义方式在实际的编程中很少使用。
例9.4 假设有三个内存字变量Oprd1,Oprd2和Oprd3,编写宏定义实现下面功能(其中:OP是除加、减之外的二元操作):
Oprd1 ← Oprd2 OP Oprd3
解:
方法1:用宏嵌套定义的方式来实现
OPMM MACRO NAME, OP
NAME MACRO OPRD1, OPRD2, OPRD3
PUSH AX
MOV AX, OPRD2
OP AX, OPRD3
MOV OPRD1, AX
POP AX
ENDM
ENDM
这时,如果要完成下列操作(其中:w1,w2和w3是内存字变量):
w1 ← w2 + w3 w1 ← w2 OR w3
那么,可按下列宏引用的方式来完成所需要的功能:
OPMM MADDM, ADD
1 MADDM MACRO OPN1, OPN2, OPN3
1 PUSH AX
1 MOV AX, OPN2
1 ADD AX, OPN3
1 MOV OPN1, AX
1 POP AX
1 ENDM ;宏引用语句(1):在宏扩展后将得到一个新的宏定义MADDM——把后二个内存字变量之和赋给第一个内存字变量:
OPMM MORM, OR
1 MORM MACRO OPN1, OPN2, OPN3
1 PUSH AX
1 MOV AX, OPN2
1 OR AX, OPN3
1 MOV OPN1, AX
1 POP AX
1 ENDM ;宏引用语句(2):在宏扩展后将得到另一个新的宏定义MORM——把后二个内存字变量之“逻辑或”赋给第一个内存字变量:
MADDM w1, w2, w3
1 PUSH AX
1 MOV AX, W2
1 OR AX, W3
1 MOV W1, AX
1 POP AX
1 ENDM ;有了宏引用语句(1)和(2)所得到的宏定义,当前宏引用语句和下一条才有效,并在宏扩展时能得到相应的程序片段。
MORM w1, w2, w3
1 PUSH AX
1 MOV AX, W2
1 OR AX, W3
1 MOV W1, AX
1 POP AX
1 ENDM
方法2:把所要运算的指令操作符作为参数进行传递
OPMM MACRO OP, OPRD1, OPRD2, OPRD3
PUSH AX
MOV AX, OPRD2
OP AX, OPRD3
MOV OPRD1, AX
POP AX
ENDM
有了上述定义之后,可按下列宏引用的方式来完成上面的“加”和“逻辑或”的功能,单击它们可显示宏扩展时所得到的程序片段:
OPMM ADD, w1, w2, w3
1 PUSH AX
1 MOV AX, w2
1 ADD AX, w3
1 MOV w1, AX
1 POP AX
OPMM OR, w1, w2, w3
1 PUSH AX
1 MOV AX, w2
1 OR AX, w3
1 MOV w1, AX
1 POP AX
从上面二种方法的比较来看,方法2显然要简单、直观些,程序的可读性和可维护性也要高一些。所以,通常情况下,宏定义的嵌套方式在实际编程过程中是很少使用的。
9.1.5 宏与子程序的区别
宏和子程序都是为了简化源程序的编写,提高程序的可维护性,但是它们二者之间存在着以下本质的区别:
1、在源程序中,通过书写宏名来引用宏,而子程序是通过CALL指令来调用;
2、汇编程序对宏通过宏扩展来加入其定义体,宏引用多少次,就相应扩展多少次,所以,引用宏不会缩短目标程序;而子程序代码在目标程序中只出现一次,调用子程序是执行同一程序段,因此,目标程序也得到相应的简化;
3、宏引用时,参数是通过“实参”替换“形参”的方式来实现传递的,参数形式灵活多样,而子程序调用时,参数是通过寄存器、堆栈或约定存储单元进行传递的;
4、宏引用语句扩展后,目标程序中就不再有宏引用语句,运行时,不会有额外的时间开销,而子程序的调用在目标程序中仍存在,子程序的调用和返回均需要时间。
总之,当程序片段不长,速度是关键因素时,可采用宏来简化源程序,但当程序片段较长,存储空间是关键因素时,可采用子程序的方法来简化源程序和目标程序。
9.2 宏参数的特殊运算符
为了宏定义和引用的某些特殊需要,汇编程序还支持几个具体特定含义的运算符。
9.2.1 连接运算符
在宏定义中,如果形式参数与其它字符连接在一起,或形式参数出现在字符串之中,那么,就必须使用连接运算符(&)。
例9.5 定义一个转移宏JUMP,其一个参数决定转移类别,另一个参数指定转移目标。
解:
JUMP MACRO CON, here
J&CON here
ENDM
假设存在下面二个引用语句,那么,点击它们可得到扩展后的指令。
JUMP mp, next
1 Jmp next
…
JUMP nz, next1
1 Jnz next1
例9.6 定义一个问候性的字符串宏GREETING,其一个参数说明字符串的变量名,另一个参数指名问候的对象。
解:
GREETING MACRO MSG, name
MSG DB 'Hello, &name'
ENDM
假设有下面引用语句,那么,点击它们将会扩展得到三个问候性的字符串定义。
GREETING STR1, 张三
1 STR1 DB 'Hello, 张三'
…
GREETING STR2, 李四
1 STR2 DB 'Hello, 李四'
…
GREETING MSG1, John
1 MSG1 DB 'Hello, John'
9.2.2 字符串整体传递运算符
字符串整体传递运算符是一对尖括号<>,用它括起来的内容将作为一个字符串来进行形式参数的整体替换。在宏引用时,如果实参内包含逗号、空格等间隔符,则必须使用该操作符,以保证实参的完整性。如果实参是某个具有特殊含义的字符,为了使它只表示该字符本身,也需要用该运算符括起来。
假设有下面定义字符串的宏DEFMSG,
DEFMSG MACRO MSG
DB '&MSG', 0DH, 0AH, '$'
ENDM
那么,使用和不使用该运算符的引用宏及其宏扩展如下所示:
DEFMSG <Are you ready?>
1 DB 'Are you ready?', 0DH, 0AH, '$'
…
DEFMSG Are you ready?
1 DB 'Are', 0DH, 0AH, '$'
9.2.3 字符转义运算符
在引用宏时,如果实参中含有特殊字符,而又要该特殊字符当作普通字符来出来,那么,就必须在该特殊字符前加上字符转义运算符“!”。
下面不使用和使用字符转义运算符的宏引用语句及其宏扩展的结果:
DEFMSG <Input one number(>90):>
1 DB 'Input one number(90):', 0DH, 0AH, '$'
;第一个“>”与字符“<”相比配,而不会把它当作“大于号”字符来处理
…
DEFMSG <Input one number(!>90):>
1 DB 'Input one number(>90):', 0DH, 0AH, '$'
;由于在第一个“>”字符前面有字符转义运算符“!”,所以,汇编程序会把第一个“>”当作“大于号”字符来处理,而把最后面的字符“>”当作是与前面“<”相比配的结束符。
9.2.4 计算表达式运算符
在引用宏时,使用计算表达式运算符“%”表示把其后面表达式的结果当作实参进行替换,而不是该表达式的整个式子。
下面使用和不使用计算表达式运算符的宏引用语句及其宏扩展的结果:
DEFMSG %200+23-100
1 DB '123', 0DH, 0AH, '$' ;先计算出表达式200+23-100的值,然后再把该值作为参数进行替换
…
DEFMSG 200+23-100
1 DB '200+23-100', 0DH, 0AH, '$' ;把整个表达式200+23-100当作一个字符串来进行参数替换
9.3 与宏有关的伪指令
在宏定义时,为了满足某种特殊需要,汇编语言还提供了几个伪指令。
9.3.1 局部标号伪指令LOCAL
在宏定义体中,如果存在标号,则该标号要用伪指令LOCAL说明为局部标号,否则,当在源程序中,有多于一次引用该宏时,汇编程序在进行宏扩展后将会给出:标号重复定义的错误。
伪指令LOCAL的一般格式如下:
LOCAL 标号1, 标号2, ……
伪指令LOCAL必须是伪指令MACRO后的第一条语句,并且在MACRO和LOCAL之间也不允许有注释和分号标志。
汇编程序在每次进行宏扩展时,总是把由LOCAL说明的标号用一个唯一的符号(从??0000到??FFFF)来代替,从而避免标号重定义的错误。
例9.7 编写求一个求绝对值的宏。
解:
方法1:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -