第十三章 高级宏汇编语言第 1节 宏指令
13.1.1宏定义与宏结束指令 1.定义宏
8086.8088宏汇编提供宏定义伪指令 MACRO.ENDM来定义宏。其格式有两种,
( 1)不带参数的宏定义
MNAME MACRO
… }宏体 ENDM
( 2)带参数的宏定义
MNAME MACRO [ DUMPAR1][,DUMPAR2] …
… }宏体 ENDM
MNAME是宏指令名字(下称宏名),代表所定义的宏。宏名是以字母开头,由字母、数字和下划线组成的字符串。
MACRO是宏定义伪指令,ENDM是宏结束伪指令。在 MACRO和
ENDM之间是一组有独立功能的程序代码,称为宏体。带参数的宏定义中,DUMPAR1,DUMPAR2,… 是形式参数(下称形参),也称哑参数,多个形参之间用逗号分隔。
经汇编后,不带参数的宏由宏体原样取代宏名 ;带参数的宏,则还要由实在参数(下称实参)取代形参。
2.宏调用与宏展开一个具有独立功能的程序段被定义为宏以后,就可以在源程序中调用了。在操作码段写上宏名,也就是使用宏指令,
称为宏调用。,先定义,后使用,是宏指令调用的原则,调用格式为 MNAME或 MNAME PAR1,PAR2,… MNAME是之前定义过的宏名。
对于带参数的宏调用,PAR1,PAR2,… 是实参。实参与形参的意义和出现次序必须一致,但参数的个数不一定相等。
当实参多于形参时,多余的实参被忽略。当实参少于形参时,
则多余的形参被忽略。
当源程序被汇编时,每当在操作码段遇到宏调用,宏汇编程序就用宏体取代宏名,对于带参数的宏,则同时用实参取代宏体中对应形参的位置,生成宏体目标代码,这个过程称为宏展开。应该注意,宏展开后,所得到的语句应该是有效的,否则宏汇编程序将会提示出错。
例 13.1字变量加法
DSEG SEGMENT
V1 DW 1234H
V2 DW 1111H
V3 DW?
DSEG ENDS
SSEG SEGMENT STACK
STK DB 20 DUP( 0)
SSEG ENDS
CSEG SEGMENT
ASSUME DS:DSEG,SS:SSEG,CS:CSEG
DADD MACRO X,Y,Z;宏定义
PUSH AX
MOVAX,X
ADDAX,Y
MOV Z,AX
POPAX
ENDM
START:MOVAX,DSEG
MOV DS,AX
MOV AX,SSEG
MOV SS,AX
MOV SP,LENGTH STK
DADD V1,V2,V3 ;宏调用
MOV AH,4CH
INT 21H
CSEG ENDS
END START
宏展开,
+ PUSH AX
+ MOV AX,V1
+ ADD AX,V2
+ MOV V3,AX
+ POP AX
从宏展开的结果可以看出,宏调用时的实参取代了宏定义时的形参。
程序清单中语句左侧的,+”号是汇编程序在清单文件中加的记号,以表示这是宏展开后的语句。请注意,这些,+”号实际上并不存在。
3.宏指令的有效范围宏指令的优先级最高。可以定义与指令助记符、伪操作同名的宏,此时,同名机器指令或伪操作失效。只有用 PURGE伪操作取消宏定义,才能恢复同名机器指令或伪操作的原始含义。 PURGE伪操作可同时取消多个宏定义,被取消的宏名之间用逗号分隔。 格式为,PURGE MNAME1 [,
MNAME2][,MNAME3] …
其中,MNAME1,MNAME2,MNAME3,… 是宏定义名,多个将被取消的宏定义名用逗号分开。
4.宏库如果希望某些宏定义能被多个程序调用,可以将这些宏定义放在一起以库文件的形式存放起来,这个库文件就是宏库。当一个源程序需要使用这些宏定义时,可以在程序中使用 INCLUDE伪指令,其语句格式为,INCLUDE HNAME HNAME是库文件名,其扩展名为,LIB。
宏汇编程序在汇编源程序时,如遇到 INCLUDE伪指令,就将它指定的库文件的内容扫描一遍,如同在源程序中进行库中的宏定义一样,因而其后的程序可以直接调用库中的宏定义。
13.1.2参数的使用
( 1)在宏定义、宏调用的参数中,经常使用的宏操作符有 4个,
① 连接操作符 &
&操作符在宏定义体中可以作为形参的前缀,宏展开时将 &前后两个符号连接形成一个符号。这个连接后的符号可以是指令助记符、操作数或是一个字符串。
②文本操作符 <>
在宏调用时,有的实参含空格或逗号,这时就必须使用文本操作符 <>
把一个完整的实参括起来,作为一个单一的实参。
③表达式操作符 %
%操作符在宏调用时用在实参的前面,格式是,%表达式汇编语言用 %后表达式的值而不是表达式文本来取代对应的形参。
④字符操作符 !
!操作符用在宏调用中,格式是,!宏操作符
!操作符告诉宏汇编程序,其后的字符不作操作符使用,而是以字符本身的意义进行处理。如,!%”表示,%”不是表达式操作符,而是一个百分号。
( 2)与宏中形参字符串对应的实参可以是数值、指令、寄存器名、存储单元名以及用寻址方式能找到的地址或表达式等。
如例 13.1中的 V1,V2可能是存储数值的内存单元地址,也可能是用等价定义伪指令定义的数值 ;V3可能是内存单元地址。
例 13.2加减法运算。参数 OP代表指令。
宏定义,
MOP MACRO OP,X,Y,Z
PUSH AX
MOV AX,X
OP AX,Y
MOV Z,AX
POP AX
ENDM
宏调用及宏展开,
MOP ADD,DA1,DA2,<WORD PTR RES>
+ PUSH AX
+ MOV AX,DA1
+ ADD AX,DA2
+ MOV WORD PTR RES,AX
+ POP AX
MOP SUB,DA1+2,DA2+2,<WORD PTR RES+2>
+ PUSH AX
+ MOV AX,DA1+2
+ SUB AX,DA2+2
+ MOV WORD PTR RES+2,AX
+ POP AX
例 13.3寄存器循环移位。参数 DIR代表指令的一部分,参数
REG代表寄存器。
宏定义,
REGROL MACRO DIR,REG,CNT
MOV CL,CNT
RO&DIR REG,CL
ENDM
宏调用及宏展开,
REGROL L,AX,8
+ MOV CL,8
+ ROL AX,CL
REGROL R,BL,4
+ MOV CL,4
+ ROR BL,CL
13.1.3宏中的标号处理在一个源程序中使用的标号必须惟一,这是编写汇编语言源程序的一个原则,否则为重复定义错误。如果宏定义体中有一个或多个标号,那么经多次宏调用后就会出现标号重复定义的情况,这是不允许的。
可以将标号定义为形参,每次宏调用时用不同的实参来取代它,从而达到避免标号重复的目的。但是对程序设计者来说,记忆用过哪些实参,
考虑该用什么样的实参,这些工作显然是多余的负担。
LOCAL伪操作是解决这个问题的有效方法,其格式是,LOCAL LAB1
[,LAB2] …
LAB1,LAB2,… 是宏定义中出现的标号。多个局部标号之间用逗号隔开。
汇编程序对 LOCAL伪指令声明的每一个标号按照它遇到的次序建立惟一的标号(0000~FFFF)。
请注意,LOCAL伪指令只能用在宏定义体内,而且它必须是 MACRO后面的第一个语句,在 MACRO和 LOCAL之间不能写任何字符,包括注释和分号标志。
13.1.4宏嵌套
1.宏定义中可以嵌套宏调用其限制条件是,先定义,后调用。此时相关的各个宏定义可以单独调用。
例 13.4非组合 BCD码转换成 ASCII码。
宏定义,
TURN MACRO
ADDAL,30H
ENDM
BTOA MACRO ADDR
XCHG AL,ADDR
TURN
XCHG AL,ADDR
ENDM
宏调用,
BTOA DATA
宏展开,
+ XCHG AL,DATA
+ ADD AL,30H
+ XCHG AL,DATA
2.宏定义中可以嵌套宏定义此时必须首先调用外层定义,然后其内层定义方可单独调用。
例 13.5字变量加减。
宏定义,
DEFMAC MACRO MACNAM,OP
MACNAM MACRO X,Y,ZPUSH AX
MOV AX,X
OP AX,Y
MOV Z,AX
POP AX
ENDM
ENDM
外层宏调用,
DEFMAC ADDITION,ADD
外层宏展开,
+ ADDITION MACRO X,Y,Z
+ PUSH AX
+ MOV AX,X
+ ADD AX,Y
+ MOV Z,AX
+ POP AX
+ ENDM
内层宏调用,
ADDITION V1,V2,V3
内层宏展开,
+ PUSH AX
+ MOV AX,V1
+ ADD AX,V2
+ MOV V3,AX
+ POP AX
13.1.5宏与子程序的区别宏与子程序都可以解决程序中对某些功能程序段多次使用的问题,减少编程工作量。但两者的区别也是很明显的,说明如下,
子程序是主程序在程序执行时由 CPU执行 CALL指令调用的,它一直是占有自身大小的一个空间。但是其为转子及返回、保存及恢复寄存器以及参数的传送等都要增加程序的开销,尤其是时间上的开销,运行速度较低。
宏是汇编程序在汇编过程中通过宏体替换宏指令展开的,经汇编连接为可执行程序,因而可以免去执行时间上的额外开销,运行速度较高。
但宏每调用一次都要把宏体展开一次,因而它占有的存储空间与调用次数有关,调用次数越多占有的存储空间也就越大。宏指令可以带形参,
调用时由实参取代,避免了子程序因变量传送导致的麻烦。在宏指令中使用的参数非常灵活,尤其是参数还可以代表指令或指令的一部分,是子程序所不及的。
两者各有长短,应根据具体情况来选择使用方案。一般来说,代码较长的功能段采用子程序方案,代码较短且参数较多的功能段采用宏方案 。
第 2节 条件汇编指令
条件汇编是指在汇编源程序时,根据用户在编程时规定的汇编条件,对程序段进行有选择的汇编。
IBM PC宏汇编程序提供了 10种条件汇编伪操作指令,它们是 IF,IF1,IF2,IFE,IF-DEF,IFNDEF,IFB,IFNB,
IFIDN,IFDIF。条件汇编语句的基本格式如下,
IFXX EXP
… }条件块 1
[ ELSE]
… }条件块 2
ENDIF
EXP是表达式。 IF后面的,XX”表示指定的条件。如果表达式满足指定的条件,宏汇编程序将汇编条件块 1的语句序列 ;否则汇编条件块 2的语句序列。
ELSE是可选部分,没有 ELSE时,如果条件满足,就汇编 IFXX与 ENDIF
间的语句块 ;否则不汇编。
IFXX的意义如下,
① IF CONT
当表达式 CONT的值不为零时,条件为真。
② IFE CONT
当表达式 CONT的值为零时,条件为真。
③ IF1
如果是第一遍扫描,条件为真。
IBM PC的宏汇编程序是一种两遍扫描程序,它两次从头至尾地读汇编源程序。第一遍用来确定每个程序行的相对偏移量,产生自定义符号表,检查语句格式和语法等。第二遍扫描产生目标程序和列表文件。
④ IF2
如果是第二遍扫描,条件为真。
⑤ IFDEF CONT
此操作中的条件 CONT是一个符号,如果这个符号已经定义,或者已用 EXTRN伪指令说明过它是个外部标号,则条件为真。
⑥ IFNDEF CONT
此操作中的条件 CONT是一个符号,如果这个符号未定义,或未经
EXTRN伪指令说明,则条件为真。
⑦ IFB <CONT>
如果尖括号中为空(不写任何字符),则条件为真。
⑧ IFNB <CONT>
如果尖括号中不为空,则条件为真。
⑨ IFIDN <CHR1>,<CHR2>
CHR1和 CHR2为字符串,如果两个字符串相同,则条件为真。两个字符串必须分别用尖括号括起来。
⑩ IFDIF <CHR1>,<CHR2>
CHR1和 CHR2为字符串,如果两个字符串不相同,则条件为真。两个字符串必须分别用尖括号括起来。
条件汇编可以用在源程序的任何位置,也允许任意次嵌套。在宏定义的条件汇编语句序列中,可以使用退出伪指令 EXITM。当宏汇编程序遇到 EXITM时,就立即跳过 ENDM伪指令。
例 13.6对于 DOS功能调用,所有的功能调用都需要在 AH寄存器中存放功能码,而其中有一些功能需要在 DX中放一个值。
定义宏指令 DOS21,要求只有在程序中定义了缓冲区时,
汇编为,
MOV AH,DOSFUN
MOV DX,OFFSET BUFF
INT 21H
否则,无 MOV DX,OFFSET BUFF指令,并展开以下宏调用:
DOS21 01
DOS21 0AH,IPFIELD
宏定义,
DOS21 MACRO DOSAH,BUF
IFE DOSAH
EXITM
ENDIF
IFDEF BUF
MOV DX,OFFSET BUF
ENDIF
MOV AH,DOSAH
INT 21H
ENDM
宏调用及宏展开,
DOS21 01
+ MOVAH,01
+ INT21H
DOS21 0AH,IPFIELD
+ MOVDX,OFFSET IPFIELD
+ MOVAH,0AH
+ INT21H
例 13.7将缓冲区中若干非组合 BCD码转换为相应 ASCII码。
条件伪操作可在宏定义中用以结束宏递归。
宏定义,
MSUM MACROADDR,N
MOV AL,ADDR+CNT
AND AL,0FH
OR AL,30H
MOV ADDR+CNT,AL
CNT=CNT+1
IF N-CNT
MSUM ADDR,CNT
ENDIF
ENDM
宏调用及宏展开,
CNT=0
MSUM DATA,3
+ MOV AL,DATA+0
+ AND AL,0FH
+ OR AL,30H
+ MOV ADDR+0,AL
+ MOV AL,DATA+1
+ AND AL,0FH
+ OR AL,30H
+ MOV ADDR+1,AL
+ MOV AL,DATA+2
+ AND AL,0FH
+ OR AL,30H
+ MOV ADDR+2,AL
第 3节 重复汇编指令
重复汇编是指在汇编源程序时,对一些语句序列进行重复的汇编,重复产生相同的或几乎完全相同的一组代码。有两类重复块伪操作指令,确定次数重复伪操作命令和不确定次数重复伪操作指令。
13.3.1确定次数重复伪指令 REPT.ENDM
格式,REPT CNT … }重复块 ENDM
REPT与 ENDM必须成对出现,CNT为重复次数,
可以是具体数值或表达式。 REPT与 ENDM之间是要重复的指令序列,称为重复块。
例 13.8缓冲区初始化。
X=0
REPT 3
X=X+1
DB X
ENDM
汇编后,
+ DB 1
+ DB 2
+ DB 3
13.3.2不确定次数重复伪指令
1.IRP.ENDM伪指令格式,IRP DUMPAR,<PAR1,PAR2,… >
… }重复块
ENDM
例 13.9缓冲区初始化
IRP X,<3,2,7,6,7>
DB X
ENDM
汇编后,
+ DB 3
+ DB 2
+ DB 7
+ DB 6
+ DB 7
2.IRPC.ENDM伪指令
IRPC.ENDM的实参必须是字符串。
格式,IRPC DUMPAR,STRING
… }重复块 ENDM
DUMPAR是形参,STRING是实参字符串。汇编时,依次用字符串中的一个字符取代形参,直到字符串的字符用完为止。
重复汇编的次数等于字符串中字符的个数。
例 13.10缓冲区初始化。
IRPC CHAR,′HELLO′
DB ′&CHAR′
ENDM
汇编后等效于,
DB ′HELLO′
第 4节 结构与记录
13.4.1结构
在大多数时候,程序处理的信息无法由字节、字或双字来表达,它们需要整合在一起。在表 7.1中,某名学生的成绩信息就是由年级、班级、学号、姓名以及语文、英语、数学三科成绩共同组成的。
宏汇编语言提供了结构定义伪指令 STRUC来实现这一功能。
1.结构的定义结构定义格式如下,
STN STRUC
FN PSD EXP1[,EXP2,… ]
∶∶
STN ENDS
STN是用户定义的结构名字。
FN是用户定义的字段名,每个结构字段名具有局部偏移量属性和类型属性。
局部偏移量是指结构字段的首字节与结构起始地址之间的字节数,类型仍是指字节、字或双字。
PSD是数据定义伪指令( DB,DW,DD,DQ或 DT)。在结构定义中的数据定义伪指令不分配内存单元,只说明各字段占用的字节、字、双字、四字等的数量及缺省值。 EXP1,EXP2,… 是相应字段的缺省值,可以是一项或多项。
2.结构变量的存储分配如前所述,结构定义只有形式而没有具体的数据,因此不分配存储单元。与结构定义对应的具体数据就是结构变量,汇编程序可以为结构变量分配内存单元。结构变量的定义与初始化格式如下,
[ SN] STN EXP
SN是用户定义的结构变量名,是任选项,它像所有的变量一样有三个属性,段属性、偏移量属性和类型属性。但类型表示的是结构的总字节数。
STN为之前定义过的结构名。
EXP是字段值表。结构变量的初始化就是对结构变量各字段的预置。字段值表用来给结构变量的字段赋初值,表中各字段的排列顺序及类型应与结构定义时一致。各字段间以逗号分隔。单项缺省值是可替代型的,
即可以用字段值表中的对应值替代。多项缺省值是不可替代型的,即不可用其他值替代。缺省值是字符串常量时,可用另一字符串替代,如果替代字符串比原字符串短,用空格填充右边空缺字符 ;如果替代字符串比原字符串长,则截去多余字符。
例 13.11用结构定义伪指令描述表 7.1
年级班级学号姓名语文英语数学 251ZHANG SAN857389252LISI
956387353WANG WU807486 SCORE STRUC
GRADE CLASS DB 2,5
NUM DB 0
NAME DB′STUDENT NAME′
CHI DB 0
EGLS DB 0
MATH DB 0
SCORE ENDS
则,
STUD SCORE<,1,′ZHANG SAN′,85,73,89> 内存中内容如下
0205
01
5A48414E4720
53414E202020
55
49
59
SCORE<,2,′LI SI′,95,63,87>
内存中内容如下
0205
02
4C4920534920
202020202020
5F
3F
57
SCORE<,3,′WANG WU′,80,74,86>
内存中内容如下
0205
03
57414E472057
552020202020
50
4A
56
3.结构变量字段的引用对于结构变量来说,引用它的字段才具有实际意义。在编程中引用结构变量字段的格式如下,
VALN.FN(变量名,字段名)
VALN是为结构变量进行存储分配时的变量名或存储器操作数的寻址方式,可以用基址、变址和基变址方式给出。
FN为引用的字段名,是在结构定义时定义的。
例如,
① MOV AL,STUD.MATH;取第一名学生的数学成绩
② MOV BX,OFFSET STUD
MOV AL,[ BX],MATH ;取第一名学生的数学成绩 MOV
AL,18[ BX],MATH ;取第二名学生的数学成绩 MOV AL,
36[ BX],MATH ;取第三名学生的数学成绩
13.4.2记录记录是宏汇编程序提供的用符号的方法定义字节或字内各位或位串的方法。
1.记录定义格式如下,
RN RECORD FN1:WT1[ =EXP1][,FN2:WT2[ =EXP2],… ]
RN是用户定义的记录名。
FN1,FN2,… 是用户定义的记录字段名。在字节或字中每个被记录定义命名的位串(至少有一位)称为记录字段。
WT1,WT2,… 是字段宽度,为一个 1~ 16间的常数。因为记录是在字节或字内进行定义,所以记录总宽度(各字段宽度的和)也在 1~ 16之间。
如果字段的总宽度小于等于 8,汇编程序将用一个字节表示记录,如果字段的总宽度大于 8而小于等于 16,汇编程序用两个字节表示记录。
EXP1,EXP2,… 是赋给相应字段的缺省初值,它是任选项。其值必须是该字段宽度能表达的正整数,若缺省,该字段的初值为 0。如果字段宽度 >7,它可以是单引号括起来的字符。
2.记录变量的存储分配与结构类似,记录定义伪指令并不分配内存单元,它只告诉汇编程序记录名及记录中各字段的名字、位置和记录的长度(字节或字)。记录变量是记录的实体,汇编程序为其进行存储分配。记录变量的定义和初始化格式如下,
[ SN] RN <[ EP1][,EP2][,… ] >
SN是用户定义的记录变量名,用于表示存储分配的第一个字节或字的符号地址,是任选项。
RN是之前定义过的记录名。
尖括号内的 EP1,EP2,… 为表达式,用以初始化记录字段的数据。它可以是常数、字符或缺省,缺省值为相应字段定义时的缺省初值。与结构的存储分配不同的是,结构的字段记录是以字节、字或双字为单位,
而记录的字段是以位为单位。记录的汇编程序把所定义的字段与字节或字的最低有效位对齐。例如,定义记录和初始化记录。
MODE RECORD A:4,B:5,C:7=3
X MODE <2,,17>
Y MODE <9,15,26>
Z MODE <,8,>
其中 MODE是记录名,在 MODE记录中分为三个字段 A,B,C,每个字段的位数分别是 4,5,7位。此记录宽度为 16,所以用一个字表示。存储单元的分配情况,
X 001 000000 0010001
2 0 17
Y 100 101111 0011010
9 15 26
Z 000 001000 0000011
0 8 3
3.在程序中取得记录的信息
①直接引用记录字段名,可取得该字段最高位的序号 ;如,MOV AX,C 则
AX=6。
②用 WIDTH运算符,可取得字段的宽度 ;如,MOV AX,WIDTH C 则 AX=7。
③用 MASK运算符,可取得字段的掩码 ;
如,MOV AX,MASK B 则 AX=0F80H。