结构和记录 为了使程序员能更方便、更有效地对数据进行组织和描述,宏汇编语言除了提供定义简单数据变量地伪指令(如DB和DW等)外,还提供了用于说明复杂数据类型地伪指令,利用这些伪指令能够说明复杂的数据类型,从而定义复杂的数据变量。
结构 结构类型的说明 在描述结构型数据或使用架构型变量之前,需要说明结构类型。用伪指令 STRUC 和 ENDS 把一系列数据定义语句包括起来就说明了一个结构体类型,一般格式如下:
1 2 3 结构名 STRUC 数据定义语句序列 结构名 ENDS
案例:一个名为 PERSON 的结构类型:
1 2 3 4 5 PERSON STRUC ID dw ? SCORE db 0 PNAME db 'ABCDEFGH' PERSON ENDS
组成结构的变量称为结构的字段,相应的变量称为字段名。
一个结构中可以含有任意数目的字段,并且各字段可以有不同的长度(基本单位是字节),还可以独立地存取。结构中的字段也可以没有字段名。
结构中的字段名代表了从结构的开始到相应字段的偏移。在说明结构体类型时,可以给字段赋初值,也可以不赋初值。
如果字段是一个字符串,那么确保其初始值有足够的长度以适应可能最长的字符串。
如下面的 MESST 的结构类型:
1 2 3 4 5 MESST STRUC MBUFF DB 100 DUP(?) CRLF DB 0DH,0AH ENDMARK DB 24H MESST ENDS
结构 MESST 中的字段 MBUFF 和 CRLF 均含有多个值。
在说明结构类型时,结构名必须是唯一的,各字段名也应该是唯一的。在说明结构类型时不进行任何存储分配,只有在定义结构变量时才进行存储分配。
注:标记一个结构类型结束的伪指令与标记一个段结束的伪指令用相同的助记符 ENDS,汇编程序通过上下文理解 ENDS 的含义,所以要确保每一个 SEGMENT 伪指令和每一个 STRUC 伪指令有各自对应的 ENDS 伪指令。
结构变量的定义 在说明了结构类型后,就可以定义相应的结构变量。结构变量定义的一般格式如下:
[变量名] 结构名 <[字段值表]>
变量名就是当前定义的结构变量的名称,结构变量名也可以省略。如果省略,那么就不能直接通过符号名访问该结构变量。
结构名是在说明结构类型时所用的名字。
字段值表用来给结构变量的各字段赋初始值,其中各字段值的排列顺序及类型应与结构定义时的各字段相一致,中间以逗号分隔。
如果某个字段采用在说明结构时所给定的缺省初值,那么可简单地用逗号表示;如果结构变量的所有字段均如此,那么可省去字段值表,但仍必须保留一对尖括号。
例如,设上述结构 PERSON,那么可定义如下结构变量:
1 2 3 4 STUDENT1 PERSON <103,88,'WANG'> ;三个字段都重赋值 STUDENT2 PERSON <104,,'LIMING'> ;字段 SCORE 仍用缺省初值 STUDENT PERSON <> ;三个字段均用缺省初值 PERSON 99 dup (<>) ;定义99各结构变量,初值不变
对宏汇编程序 MASM 而言,如果某个字段有多个值,那么在定义结构变量时,就不能给该字段赋初始值。如上面说明地结构 MESST,不饿能给 MBUFF 字段和CRLF字段重赋初值。 下面定义结构变量的语句:
1 2 MESS1 MESST <> MESS2 MESST <,,0>
结构变量及其字段的访问 通过结构变量名可直接存取结构变量。若要存取结构变量中的某一字段,则可采用如下形式:
结构变量名.结构字段名
结构变量与结构字段名中间用点号分隔,并且结构字段名所代表的字段必须是对应结构所具有的字段。这种形式表示的变量的地址偏移值是结构变量地址(起始地址)的偏移值与相应字段偏移值之和。
案例:演示结构变量的直接寻址和变址寻址(相对基址)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ;程序名:T7-1.asm ;功能:演示结构变量的直接寻址和变址寻址(相对基址) assume cs:code,ds:data DATE STRUC YEAR DW ? MONTH DB ? DAY DB ? DATE ENDS data segment YESTERDAY DATE <1995,7,17> TODAY DATE <1995,7,18> TOMORROW DATE <1995,7,19> data ends code segment start: mov ax,data mov ds,ax ;直接寻址 mov AL,YESTERDAY.DAY mov ah,TODAY.MONTH mov TOMORROW.YEAR,dx ;变址寻址 mov bx,offset YESTERDAY mov al,[bx].MONTH code ends end start
例1:数据文件 SCORE.DAT 中一次存放着30个学生的成绩记录,文件(成绩)记录具有如下字段:
1 2 3 4 5 学号 整数 2字节 姓名 字符串 8字节 语文成绩 整数 1字节 数学成绩 整数 1字节 外语成绩 整数 1字节
写一个程序计算三门课程的总分,把学号和总分依次写到文件 SCORE.SUM 中。SCORE.SUM 文件记录两个字段,第一个字段是学号,第二个字段是总分(用2字节表示)。 实现流程是:
打开文件 SCORE.DAT;
循环处理每个学生的成绩,把学号和总分放到缓冲区中;
关闭文件 SCORE.DAT
新建文件 SCORE.SUM
把缓冲区的内容写入文件 SCORE.SUM
关闭文件 SCORE.SUM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 ;程序名:T7-2.asm ;功能:写一个程序计算三门课程的总分,把学号和总分依次写到文件 SCORE.SUM 中。SCORE.SUM 文件记录两个字段,第一个字段是学号,第二个字段是总分(用2字节表示)。 assume cs:code,ds:data ;定义常量 COUTN = 30 ;原始成绩结构体 SCORE SCORE STRUC IDS dw ? ;学号 SNAME db 8 dup (' ') ;姓名 LANG db 0 ;语文成绩 MATH db 0 ;数学成绩 ENG db 0 ;英语成绩 SCORE ENDS ;对应学号、姓名和总分的 ITEM 的定义 ITEM STRUC IDD dw ? ;学号 SUM dw 0 ;总分,总分300会超过1字节空间。 ITEM ENDS data segment BUFFER SCORE <> ;存放原始成绩的缓冲区 STABLE ITEM COUTN dup (<>) ;预留存储总表的缓冲区 FNAME1 db 'SCORE.DAT',0 ;原始文件名 FNAME2 db 'SCORE.SUM',0 ;存储新的数据文件名 data ends code segment start: mov ax,data mov ds,ax mov si,offset STABLE ;STABLE 需要变址寄存器存储数据 ;打开文件 SCORE.DAT mov dx,offset FNAME1 mov ax,3d00h int 21h ;以只读方式打开 SCORE.DAT jc stop ;文件打开失败,终止程序 ;一次性读13个字节,初始化各寄存器 mov bx,ax mov cx,13 mov dx,offset BUFFER READ: mov ah,3fh int 21h ;判断有没有读取到字符,没有读到就说明已经读到文件尾了。 cmp ax,cx jnz clsf1 ;将结果存储到 STABLE 中 mov ax,BUFFER.IDS mov [si],ax xor ax,ax mov al,BUFFER.LANG add al,BUFFER.MATH add al,BUFFER.ENG adc ah,0 add si,TYPE STABLE.IDD mov [si],ax ; add si,TYPE STABLE.SUM jmp READ clsf1: ;关闭文件 SCORE.DAT mov ah,3eh int 21h cmp word ptr STABLE,0 jz stop ;如果缓冲区中未读取任何数据,结束程序 ;新建 SCORE.SUM mov dx,offset FNAME2 mov cx,0 mov ax,3c00h int 21h ;写数据需要变址寄存器 mov si,offset STABLE ;写数据 mov bx,ax mov cx,4 WRITE: ;判断有没有写到缓冲区的最后一个字符 cmp word ptr [si],0 jz clsf2 mov dx,si mov ah,40h int 21h add si,TYPE ITEM jmp WRITE clsf2: mov ah,3eh int 21h stop: mov ax,4c00h int 21h code ends end start
在程序 T7-2.asm 中,定义了两个结构 SCORE 和 ITEM,用来描述文件记录的字段组成。利用 SCORE 定义了存放原始成绩记录的缓冲区。利用 ITEM 定义了一张总表,然后借助指向总分表当前项的指针访问当前的学号和总分字段。此外还利用了 TYPE 得到结构的字节数。
例2:写一个求字符串长度的子程序。子程序调用过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 ;程序名:T7-3.asm ;功能:写一个求字符串长度的子程序。子程序调用过程如下: ;子程序名:STRLEN ;功 能:检测字符串长度 ;入口参数:字符串首地址的段值和偏移在堆栈顶 ;出口参数:AX=字符串长度 ;说明:(1)字符串以0结尾;字符串长度不包括结尾标志。 ; (2)本过程是一个远过程 assume cs:code,ds:data ;堆栈结构体 PARM PARM STRUC BPREG DW ? ;对应 bp 寄存器保存单元 RETADR DD ? ;对应返回地址 STROFF DW ? ;对应入口参数中的偏移 STRSEG DW ? ;对应入口参数中的段值 PARM ENDS data segment string1 db 'Hello World!',0 data ends code segment start: mov ax,data mov ds,ax mov ax,seg string1 push ax mov ax,offset string1 push ax call far ptr STRLEN mov ax,4c00h int 21h ;------------------------------------------------------- ;子程序名:STRLEN ;功 能:检测字符串长度 ;入口参数:字符串首地址的段值和偏移在堆栈顶 ;出口参数:AX=字符串长度 ;说明:(1)字符串以0结尾;字符串长度不包括结尾标志。 ; (2)本过程是一个远过程 ;-------------------------------------------------------- STRLEN PROC FAR PUSH BP MOV BP,SP PUSH DS PUSH SI MOV DS,[BP].STRSEG ;取字符串首地址的段值 MOV SI,[BP].STROFF ;取字符串首地址的偏移 XOR AL,AL STRLEN1: CMP BYTE PTR [SI],AL JZ STRLEN2 INC SI JMP STRLEN1 STRLEN2: MOV AX,SI SUB AX,[BP].STROFF POP SI POP DS POP BP RET STRLEN ENDP code ends end start
记录 记录类型为按二进制位存取数据或信息提供了方便。
记录类型的说明 在描述记录型数据或使用记录型变量之前,需要说明记录类型。伪指令 RECORD 用于说明记录类型,一般格式如下:
记录名 RECORD 字段 [,字段…]
记录名标识说明记录类型:字段表示构成记录的字段的名字、宽度和初值。每一字段的格式如下:
字段名:宽度[=表达式]
字段名是记录字段的名字。宽度表示相应字段所占的位数,宽度必须是常数,宽度最大为 16 位。表达式的值将作为相应字段的初值。如果初值的数据宽度大于宽度,则汇编时将会产生错误提示信息。如果某个字段没有初值,那么缺省的初值被置为0.
一个记录可以含有多个字段,字段间用逗号分隔。但在一般情况下各字段的宽度之和不超过 16。例如:
COLOR RECORD BLINK:1,BACK:3,INTENSE:1,FORE:3
上述记录类型 COLOR 含四个字段(BLINK、BACK、INTENSE、FORE),各字段均没有初值,他们的宽度分别是1、3、1、3。这四个字段所占总宽度正好是 8 位,所以也称为字节记录类型。这四个字段的具体意义如下图所示:
BLINK(闪烁)占1各数据位,BACK(背景色)占3个数据位,INTENSE(亮度)占1个数据位,FORE(前景色)占3个数据位。
注:在说明记录类型时,不实际分配存储单元
如果一个记录中所有说明字段的总宽度大于 8,那么汇编程序会给对应的记录变量分配两字节,否则仅给对应的记录变量分配一字节。第一个字段放在记录左边的较高有效位,随后说明的字段放在右边后续位上,如果说明的字段总宽度不正好是8位或16位,那么向右对齐,记录高端未说明的位置为0。
例如:
ABCD RECORD AA:5=12,BB:3=6,CC:4=3
上述记录类型 ABCD 含三个字段,这三个字段所占各位如下图所示:
一共是 12 位,因不满 16 位,在最高位补 4 个 0
记录变量的定义 定义记录变量的一般格式如下:
[变量名] 记录名 <[字段值表]>
变量名就是当前定义的记录变量的名称,记录变量名可以省略,如果省略,那么就不能直接通过符号名访问该记录变量。
记录名是在说明记录类型时所用的名字。
字段值表用来记录变量的各字段初值,各字段值的排列顺序及大小应与记录说明时的各字段相一致,中间已逗号分隔。如果某个字段采用在说明记录时所给定的初值,那么可简单使用逗号表示;如果记录变量的所有字段均如此,那么可省去字段值表,但仍必须保留一对尖括号。
例如:定义上述记录类型 COLOR,那么可定义如下结构变量:
1 2 3 WARING COLOR <1,0,1,4> ;该字节值是 8CH COLOR <,3,,110B> ;该字节是 36H COLORST COLOR 32 dup(<>) ;32 个字节
注:如果有 7 位宽时,可定义为一字符
记录专用操作符 操作符 WIDTH 和 MASK 仅与记录一起使用,得到已说明记录的不同方面的常数值。
(1)操作符 WIDTH 操作符 WIDTH 返回记录或记录中字段以位为单位的宽度。一般格式如下:
1 2 3 WIDTH 记录名 或者 WIDTH 记录字段名
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ;程序名:T7-4.asm ;功能:记录变量操作演示 assume cs:code,ds:data COLOR RECORD BLINK:1,BACK:3,INTENSE:1,FORE:3 data segment WARING COLOR <1,2,1,4> data ends code segment start: xor ax,ax mov al,WARING ; sub al,WIDTH COLOR mov DH,WIDTH BACK ADD BH,WIDTH INTENSE code ends end start
(2)操作符 MASK 一般格式:
操作符 MASK 返回一个 8 位或 16 位二进制数,指定字段各个位置为1,其余位为0。如果记录是字节记录类型,那么就是一个 8 位二进制数,如果记录类型是字记录类型,那么就是一个 16 位二进制数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ;程序名:T7-4.asm ;功能:记录变量操作演示 assume cs:code,ds:data COLOR RECORD BLINK:1,BACK:3,INTENSE:1,FORE:3 ABCD RECORD AA:5=12,BB:3=6,CC:4=3 data segment WARING COLOR <1,2,1,4> data ends code segment start: xor ax,ax mov al,WARING ;WIDTH sub al,WIDTH COLOR mov DH,WIDTH BACK ADD BH,WIDTH INTENSE ;MASK mov al,MASK BLINK ;BLINK 1位置1,其余为0 or al,MASK FORE ;FORE 3位置1,其余为0 and dx,MASK ABCD ;ABCD 置1 code ends end start
(3)记录字段 记录字段名作为一个特殊的操作符,它不带操作数,直接返回该字段移到所在记录的最右端所需移动的位数。
1 2 mov al,BLINK mov cl,INTENSE
记录及其字段的访问 由于 8086/8088没有位操作指令,记录类型和记录操作符只能够提供访问记录中字段的便利。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 ;程序名:T7-5.asm ;功能:记录及其字段的访问 assume cs:code,ds:data COLOR RECORD BLINK:1,BACK:3,INTENSE:1,FORE:3 ;说明记录类型 data segment CHAR db 'A' ATTR COLOR <0,0,1,7> ;定义记录变量 data ends code segment start: mov bp,1 shl WIDTH BACK ;置循环计数器 next: mov ah,9 ;在当前光标位置显示字符 mov bh,0 mov al,CHAR mov bl,ATTR mov cx,1 int 10h mov al,ATTR ;取显示属性(记录变量) mov ah,al and al,not MASK BACK ;析出除背景的其他位 mov cl,BACK shr ah,cl ;把背景字段移至右端 inc ah ;调整背景色 shl ah,cl ;再向左移到原位 and ah,MASK BACK ;屏蔽除背景位的其他位 or ah,al ;和其他原值合并 mov ATTR,ah ;保存属性 ;接收键盘输入 mov ah,0 int 16h dec bp jnz next ; mov ax,4c00h int 21h code ends end start
宏 宏是宏汇编语言的主要特征之一。在汇编语言源程序中,若某程序片段需要多次使用,为了避免重复书写,那么可以把他定义为一条宏指令。在写源程序时,程序员用宏指令来表示程序片段;在汇编时,汇编程序用对应的程序片段代替宏指令。
宏指令的定义和使用 宏指令在使用之前要先定义。宏定义一般格式如下:
1 2 3 4 宏指令 MACRO [形式参数] . . ENDM
其中,MACRO 和 ENDM 是一对伪指令,在宏定义中,它们必须成对出现,表示宏定义的开始和宏定义的结束。MACRO 和 ENDM 之间的内容称为宏定义体,可以是由指令、伪指令和宏指令构成的程序片段。宏指令名由用户指定,使用一般标号命名规则。可选的形式参数表可由若干个参数组成,各形式参数间用逗号分隔。
例如:把将 AL 寄存器内的低4位转换为对应十六进制数 ASCII 码的程序定义为一个宏:
1 2 3 4 5 6 7 HTOASC AMCRO AND AL,0FH ADD AL,90H DAA ADC AL,40H DAA ENDM
比如把 DOS 的 1 功能调用从键盘读一个字符的程序片段定义为一个宏
1 2 3 4 GETCH MACRO MOV AH,1 INT 21H ENDM
在定义宏指令后,就可以使用宏指令来表示对应的程序片段,这称为宏调用。宏调用一般格式如下:
其中,实参数表中的实参数应该与宏定义时的形式参数表中的形式参数相对应。
下面的程序调用了两个刚定义的两个宏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ;程序名:T7-6.asm ;功能:演示宏汇编代码 assume cs:code HTOASC MACRO AND AL,0FH ADD AL,90H DAA ADC AL,40H DAA ENDM GETCH MACRO MOV AH,1 INT 21H ENDM code segment start: GETCH mov ah,al shr al,1 shr al,1 shr al,1 HTOASC xchg ah,al HTOASC ; mov ax,4c00h int 21H code ends end start
对源程序汇编时,汇编程序把源程序中的宏指令替换成对应的宏定义体,这称为宏展开或宏扩展。
宏汇编指令不能放在程序结尾 END START 之后。
宏指令的用途 缩短源代码 若在源程序中要多次使用到某个程序片段,那么就可以把此程序片段定义为一条宏指令。例如,把使光标另起一行的程序片段写成如下宏:
1 2 3 4 5 6 7 8 CRLF MACRO XOR BH,BH MOV AH,14 MOV AL,0DH INT 10H MOV AL,0AH INT 10H ENDM
扩充指令集 CPU 的指令集是确定的,但利用宏能在汇编语言中在形式上崔指令进行扩充。扩充后的指令集是机器指令集与宏指令集的并集。这不仅能方便源程序的编写,而且便于理解源程序。 例如,把8个通用寄存器全部压入堆栈的功能:
1 2 3 4 5 6 7 8 9 10 PUSHA MACRO PUSH AX PUSH BX PUSH CX PUSH DX PUSH SP PUSH BP PUSH SI PUSH DI ENDM
改变某些指令助记符的意义 宏指令可以与指令助记符或伪操作指令名相同,在这种情况下,宏指令的优先级最高,而同名的指令或伪操作指令就失效了。利用宏指令的这一点,可以改变指令助记符的意义。 例如,在定义如下宏指令后,助记符 LODSB 所表示指令的意义就变化了:
1 2 3 4 LODSB MACRO MOV AH,[SI] INC SI ENDM
定义与指令同名的宏指令后,根据编译器的不同,可能会出现警告信息。
宏指令中参数的使用 宏指令可以不带参数,如上面定义的宏指令 GETCH 和 PUSHA 等。但往往带参数的宏指令更具有灵活性。
宏指令的参数很灵活 (1)宏指令的参数可以是常数、寄存器和存储单元,也可以是表达式。 例1:在逻辑左指令 SHL 的基础上定义一条宏指令 SHLN,它能实现指定次数的左移。
1 2 3 4 5 6 SHLN MACRO REG,NUM PUSH CX MOV CL,NUM SHL REG,CL POP CX ENDM
宏指令调用:
1 2 3 SHLN BL,5 SHLN SI,9 SHLN AX,CL
(2)宏指令的参数可以是操作码 例2:下面的宏指令 MANDM 有三个参数,第一个参数 OPR 作为操作符使用:
1 2 3 4 5 MANDM MACRO OPR,X,Y MOV AX,X OPR AX,Y MOV X,AX ENDM
宏调用参数个数可以与定义时不一致 一般来说,宏调用时使用的实参个数应该与宏定义时的形参一致,但汇编程序并不要求它们必须相等。
若实参个数多余形参个数,那么多余的实参被忽略。
若实现的个数少于形参个数,那么多余的形参用 “空” 代替。
另外必须注意,宏展开后即实参取代形参后,所得的语句必须是有效的,否则汇编程序将会提示出错。
例如,使用宏指令 MANDM SUB,var1,var2,var3
后,多余的参数 VAR3 被忽略了。
特殊的宏运算 为了方便宏的定义和调用,汇编程序还支持特殊的运算符,它们适用于宏的定义或调用,还适用于重复块。支持的特殊运算符如下表所示:
强迫替换运算符 “&” 在宏定义中,若参数紧跟在其它在字符前或后,或者参数出现带引号的字符串中时,就必须使用该运算符,以区分参数。
例1:宏指令 jump 中,参数 CON 作为操作码的部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ;程序名:T7-8.asm ;功能:演示 强迫替换运算符& 的使用 assume cs:code JUMP MACRO CON,LAB J&CON LAB ENDM code segment start: JUMP NZ,next ; next: mov ax,4c00h int 21H code ends end start
例2:下面定义的宏 MSGGEN 中,两个参数合并成标号,一个参数用在字符串中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ;程序名:T7-8-1.asm ;功能:演示 强迫替换运算符& 的使用 assume cs:code,ds:data MSGGEN MACRO LAB,NUM,XYZ LAB&NUM db 'HELLO MR.&XYZ',0DH,0AH,24H ENDM data segment MSGGEN MSG,1,TAYLOR data ends code segment start: mov ax,data mov ds,ax ; mov ah,9 mov dx,offset MSG1 int 21h next: mov ax,4c00h int 21H code ends end start
字符串原样传递运算符 “<>” 字符串原样传递运算符是一对尖括号,在宏调用、重复块和条件汇编中,由它括起的内容作为一个字符串。在宏调用时,若实参包含逗号或空格等间隔符,则必须使用该运算符,以保证实参的完整性。若实参是某个有特殊意义的字符,为了使它只表示字符本身,也可以使用该运算符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ;程序名:T7-9.asm ;功能:演示 字符串原样传递运算符 “<>” 的使用 assume cs:code,ds:data DFMESS MACRO MESS db '&MESS',0DH,0AH,0 ENDM data segment DFMESS <This is a example> data ends code segment start: mov ax,data mov ds,ax ; mov ax,4c00h int 21H code ends end start
文字字符运算符 “!” 该运算符使其后的一个字符只作为一般字符。在宏调用时,如果实参中含有一些特殊字符,为了使这些字符作为一般字符来处理,那就必须在其前写上该字符。
例如:利用上述的宏 DFMESS 定义字符串 “Can not enter > 99”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ;程序名:T7-10.asm ;功能:演示 文字字符运算符 “!” 的使用 assume cs:code,ds:data DFMESS MACRO MESS db '&MESS',0DH,0AH,0 ENDM data segment DFMESS <Can not enter > 99> DFMESS <Can not enter !> 99> data ends code segment start: mov ax,data mov ds,ax ; mov ax,4c00h int 21H code ends end start
表达式运算符 “%” 在宏调用时,使用该运算符能把其后表达式的结果作为实参替换,而非表达式自身。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ;程序名:T7-11.asm ;功能:演示 表达式运算符 “%” 的使用 assume cs:code,ds:data DFMESS MACRO MESS db '&MESS',0DH,0AH,0 ENDM data segment DFMESS %(12+3-4) DFMESS 12+3-4 data ends code segment start: mov ax,data mov ds,ax ; mov ax,4c00h int 21H code ends end start
宏注释 在宏定义中,如果注释以两个分号引导,那么扩展时该注释不会出现。
宏与子程序的区别 采用宏和子程序这两种方法均能达到简化源程序的目的。但是这两者之间存在质的不同。
宏调用是通过宏指令名进行的,在汇编时,由汇编程序把宏展开,有多次宏调用,就有相应次的宏扩展,因此并不简化目标程序;子程序调用是在程序执行期间执行 CALL 指令进行的,子程序代码只在目标程序中出现一次,所以目标程序也得到相应的简化。
宏调用时的参数由汇编程序通过实参替换形参的方式实现传递,所以参数很灵活;子程序调用时的参数需通过寄存器,堆栈或约定的内存单元传递。
宏调用是在汇编时完成,所以不需要额外的时间开销;子程序调用和子程序返回均需要时间,且涉及堆栈。
总之,当程序片段不长,速度是主要矛盾时,通常采用宏只能够的方法简化源程序;当程序片段较长,额外操作所附加的时间就不明显,而节约存储空间是主要矛盾时,通常采用子程序的方法简化源程序和目标程序。
与宏有关的伪指令 局部变量说明伪指令 LOCAL 在宏定义体中可以使用标号。例如:
1 2 3 4 5 6 7 8 HTOASC MACRO AND AL,0FH CMP AL,9 JBE ISDECM ADD AL,7 ISDECM: ADD AL,30H ENDM
如果在程序中多次调用上述宏 HTOASC,汇编时将出现重复定义错误,如下图所示:
原因是每次展开宏 HTOASC 都得到一个标号 ISDECM。为此,汇编提供了伪指令 LOCAL,供程序员说明宏的局部标号。
伪指令 LOCAL 的一般格式如下:
LOCAL 标号表
标号表由标号构成,标号间用逗号隔开。汇编程序在每次展开宏时,总把由 LOCAL 伪指令说明的标号用唯一的符号(??0000至??FFFF)代替,从而避免标号重定义错误。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ;程序名:T7-12.asm ;功能:演示 局部变量说明伪指令 LOCAL 的使用 assume cs:code HTOASC MACRO LOCAL ISDECM AND AL,0FH CMP AL,9 JBE ISDECM ADD AL,7 ISDECM: ADD AL,30H ENDM code segment start: xor ax,ax mov al,7 HTOASC mov al,5 HTOASC ; mov ax,4c00h int 21H code ends end start
必须注意,LOCAL 伪指令用在宏定义体内,而且必须是伪指令 MACRO 后的第一条语句,在 MACRO 和 LOCAL 伪指令之间不允许有注释和分号标志。
清除宏定义伪指令 PURGE 伪指令 PURGE 的作用是告诉汇编程序取消某些宏。取一般格式如下:
PURGE 宏名表
宏名表由宏名构成,宏名之间用逗号分隔。汇编程序在遇到 PURGE 伪指令后,就取消由宏名表所列出的宏定义,此后不再扩展这些宏。 例如:定义如下宏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ;程序名:T7-13.asm ;功能:演示 清除宏定义伪指令 PURGE 的使用 assume cs:code LODSB MACRO MOV AH,[SI] INC SI ENDM code segment start: LODSB PURGE LODSB LODSB ; mov ax,4c00h int 21H code ends end start
微软的汇编器不允许这样用,因此编译之后原 LODSB 指令是没有的。
终止宏扩展伪指令 EXITM 伪指令 EXITM 通知汇编程序结束当前宏调用的扩展。一般格式如下:
EXITM
当遇到 EXITM 时,汇编程序立即退出宏,在宏剩下的语句不被扩展。如果在一嵌套的宏内遇到伪指令 EXITM,则退出到外层宏。 伪指令 EXITM 通常与条件伪指令一起使用,以便在规定的条件跳过宏内的最后的语句。
宏定义的嵌套 宏定义体中调用宏 宏汇编语言允许在宏定义体中使用宏调用,其限制条件仍是:必须先定义后调用。 如下宏 WHTOASC 的定义体内就调用了宏 HTOASC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ;程序名:T7-14.asm ;功能:演示 嵌套宏 的使用 assume cs:code HTOASC MACRO AND AL,0FH ADD AL,90H DAA ADC AL,40H DAA ENDM WHTOASC MACRO MOV AH,AL SHR AL,1 SHR AL,1 SHR AL,1 SHR AL,1 HTOASC XCHG AH,AL HTOASC ENDM code segment start: MOV AX,0102H WHTOASC ; mov ax,4c00h int 21H code ends end start
宏定义体中定义宏指令 宏定义体中还可含有宏定义,但只有在调用外层的宏后,才能调用内层的宏。原因是只有在调用了外层的宏后,内层的宏定义才有效。
下面的宏 DEFMAC 含有一个宏定义,并且外层的宏参数 MACNAME 是内层的宏指令名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ;程序名:T7-14-1.asm ;功能:演示 嵌套宏 的使用 assume cs:code,ds:data DEFMAC MACRO MACNAME,OPERATOR MACNAME MACRO X,Y,Z PUSH AX MOV AX,X OPERATOR AX,Y MOV Z,AX POP AX ENDM ENDM data segment var1 dw 1 var2 dw 1 result dw 0 data ends code segment start: DEFMAC ADDITION,ADD ADDITION var1,var2,result ; DEFMAC SUBTRACT,SUB ; DEFMAC LOGOR,OR mov ax,4c00h int 21H code ends end start
宏指令 DEFMAC 的参数 MACNAME 作为内部嵌套宏的指令名,从而实现 DEFMAC 执行之后同时声明了宏 ADDITION 指令。
重复汇编 有时程序中会连续地重复执行完全相同或几乎相同地一组语句时,当出现这种情况时,可考虑重复伪指令定义地重复块,以简化源程序。 重复块是允许建立在重复语句块的宏的一种特殊形式。它们与宏的不同之处在于它们没有被命名,并因而不能被调用。但像宏一样,它们可以有参数,且在汇编过程中参数可被变量替换;宏运算符、用伪指令 LOCAL 说明的符号等可用在重复块中;重复块总是由伪指令 ENDM 结束。
伪指令 REPT 伪指令 REPT 用于创建重复块,重复块的重复次数由一数值表达式给定。一般格式如下:
宏汇编程序会把 “需要重复的语句组” 连续地重复汇编,由表达式值决定重复此时。
表达式必须可求出数值常数。
任何有效汇编程序语句均可安排在 “需重复语句组” 中。
例如:把字符 A 到 Z 的 ASCII 码填入数组 TABLE 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ;程序名:T7-15.asm ;功能:演示 伪指令 “REPT” 的使用 assume cs:code,ds:data CHAR = 'A' data segment TABLE LABEL BYTE REPT 26 ;重复块开始,规定重复次数 DB CHAR ;需要重复的语句1 CHAR = CHAR+1 ;需要重复的语句2 ENDM ;结束块重复 data ends code segment start: mov ax,data mov ds,ax ; mov ax,4c00h int 21H code ends end start
伪指令 IRP 伪指令 IRP 用于创建重复块,重复次数和每次重复时使用的实参由实参数列表决定。一般格式如下:
1 2 3 IRP 形式参数,<实参1,实参2,....,实参n> 重复语句块 ENDM
实参的个数决定了重复的次数。
宏汇编程序会把 “需要重复的语句组” 连续地重复汇编规定的次数,并在每次重复时一次用相应位置的实参代替 “需重复语句组” 中的形式参数。
实参数列表应放在一对尖括号内,若有多个实参数,则各实参数间用逗号分隔。
例如:把 0~9 的平方值存入数组 QUART 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ;程序名:T7-15-1.asm ;功能:演示 伪指令 IRP 的使用 assume cs:code,ds:data data segment ;把 0~9 的平方值存入数组 QUART 中 QUART LABEL BYTE IRP x,<0,1,2,3,4,5,6,7,8,9> ;重复块开始,尖括号中的参数规定重复次数 DB x*x ;需要重复的语句1 ENDM ;结束块重复 data ends code segment start: mov ax,data mov ds,ax ; mov ax,4c00h int 21H code ends end start
再如:把若干个寄存器值压入堆栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ;程序名:T7-15-1.asm ;功能:演示 伪指令 IRP 的使用 assume cs:code,ds:data data segment ;把 0~9 的平方值存入数组 QUART 中 QUART LABEL BYTE IRP x,<0,1,2,3,4,5,6,7,8,9> ;重复块开始,尖括号中的参数规定重复次数 DB x*x ;需要重复的语句1 ENDM ;结束块重复 data ends code segment start: mov ax,data mov ds,ax ;把若干个寄存器值压入堆栈 IRP REG,<AX,BX,CX,DX> push REG ENDM ; mov ax,4c00h int 21H code ends end start
伪指令 IRPC 伪指令 IRPC 与 伪指令 IRP 相似,但实参数列表是一个字符串,一般格式如下:
1 2 3 IRPC 形式参数,字符串 需要重复的语句组 ENDM
字符串的长度规定了重复的次数。
宏汇编程序会把 “需要重复的语句组” 连续地重复汇编规定地次数,并在每次重复时一次用 “字符串” 中的一个字符作为参数代替 “需要重复语句组” 中的形式参数。
如果字符串含有空格、逗号等分隔符,那么字符串需要用尖括号括起来。
例如:把从 2 开始的 10 个偶数存入字数组 TABLE 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ;程序名:T7-15-2.asm ;功能:演示 伪指令 IRPC 的使用 assume cs:code,ds:data data segment ;把从 2 开始的 10 个偶数存入字数组 TABLE 中 TABLE LABEL BYTE IRPC x,0123456789 ;重复块开始,字符串个数规定重复次数 dw (x+1)*2 ;需要重复的语句1 ENDM ;结束块重复 data ends code segment start: mov ax,data mov ds,ax ; mov ax,4c00h int 21H code ends end start
条件汇编 条件汇编语句提供根据某种条件决定是否汇编某段源程序的功能。 再源程序中使用条件汇编语句的主要目的是: (1)通过在汇编前或汇编时改变某种体哦啊急啊 ,从而方便地产生不同地程序; (2)增强宏定义能力,使得宏地适用范围更广 (3)改变汇编效率。
尽管条件汇编语句在形式上与高级语言中地条件语句相似,但本质上却完全不同。条件汇编语句是说明性语句,是由伪指令构成,它地功能是由编译器实现;一般高级语言的条件语句是执行语句,它的功能由目标程序实现。
条件汇编伪指令 条件汇编语句的一般格式如下:
1 2 3 4 5 IFxxxx 条件表达式 语句组1 [ELSE 语句组2] ENDIF
IFxxxx 是条件伪指令助记符的一般形式,其中 xxxx 表示构成条件语句助记符的其他字符。完整的条件伪指令助记符如下:
1 2 IF IFE IFDEF IF1 IF2 IFB IFNB IFIDN IFDIF
一定要在条件语句最后安排伪指令 ENDIF。
条件汇编语句的意义为:如果条件伪指令要求的条件满足,那么就编译语句组 1,否则编译 ELSE 中的语句组 2。同时由于 “语句组1” 和 “语句组2” 可再含有条件语句,因此可以形成条件汇编语句的嵌套。一个嵌套的 ELSE 伪指令总是与最近但又没有 ELSE 的 IFxxxx 伪指令相匹配。
伪指令 IF 和 IFE 伪指令 IF 的一般格式如下:
IF 表达式
如果表达式的值不等于0,条件为真。表达式不能包含向前引用,其结果应为一个常数值。
伪指令 IFE 的一般格式如下:
如果表达式的值等于 0,条件为真。表达式不能包含向前引用,其结果应为一个常数值。
例1:在下面的条件语句中,如果 MFLAG 值不为 0,即条件满足,那么就编译 “语句组1”,否则编译 “语句组2”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ;程序名:T7-16.asm ;功能:演示 伪指令 IF 的使用 assume cs:code MFLAG = 0 code segment start: if MFLAG mov ah,0 int 16h ;当 MFLAG 值不为 0 时,编译此语句组 else mov ah,1 int 21h ;当 MFLAG 值为 0 时,编译此语句组 endif ; mov ax,4c00h int 21H code ends end start
例2:在下面的条件语句中,条件表达式是一个关系表达式,根据关系表达式的求值方法,如果 PORT 值为 0,则关系表达式的值为1,不为0所以条件满足。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ;程序名:T7-16-1.asm ;功能:演示 伪指令 IF 的使用 assume cs:code,ds:data PORT = 0 data segment if PORT EQ 0 PORTADDR = 3F8H IVECTN = 0BH IMASKV = 11110111B endif data ends code segment start: mov ax,data mov ds,ax ; mov ax,PORTADDR mov bx,IVECTN mov cx,IMASKV ; mov ax,4c00h int 21H code ends end start
例3:如下定义的宏 SHIFTL 使用了重复块和结束宏扩展伪指令 EXITM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ;程序名:T7-16-2.asm ;功能:演示 伪指令 IF 的使用 assume cs:code SHIFT MACRO OP,N COUNT = 0 REPT N SHL OP,1 COUNT = COUNT+1 if COUNT GE N ;如果 COUNT >= N,就停止编译 EXITM endif ENDM INC OP ENDM code segment start: SHIFT AX,1 ; SHIFT BX,3 mov ax,4c00h int 21H code ends end start
伪指令 IFDEF 和 IFNDEF 伪指令 IFDEF 的一般格式如下:
IFDEF 符号
如果符号已定义或被说明成外部符号,则条件为真。
伪指令 IFNDEF 的一般格式如下:
IFNDEF 符号
如果符号未定义或未被说明成外部符号,则条件为真。
例如:在下面的条件语句中,如果已先定义符号 MLARGE,则条件满足,那么 AXINC 被定义为远过程,否则过程 AXINC 被定义成近过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ;程序名:T7-17.asm ;功能:演示 伪指令 IFDEF 的使用 assume cs:code MLARGE = 0 code segment ifdef MLARGE AXINC PROC FAR ;若已定义 MLARGE 则汇编此语句 else AXINC PROC NEAR ;若未定义 MLARGE 则汇编此语句 endif INC AX ;不受影响 RET ;不受影响 AXINC ENDP start: call FAR ptr AXINC ; mov ax,4c00h int 21H code ends end start
符号可以在源程序中定义,也可以在汇编命令行中定义。
伪指令 IF1 和 IF2 伪指令 IF1 的格式如下:
IF1
若是第一趟扫描则条件为真。
伪指令 IF2 的格式如下:
IF2
若是第二趟扫描则条件为真。
条件汇编与宏结合 条件汇编与宏相结合,能大大扩大宏的使用范围。 例如:如下定义的宏 ADDNUM 有两个参数,在对宏调用扩展时,能根据不同的参数扩展成不同的指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ;程序名:T7-18.asm ;功能:演示 宏中使用条件汇编 assume cs:code ADDNUM MACRO REG,NUM if (NUM GT 2) OR (NUM LE 0) ADD REG,NUM else INC REG if NUM EQ 2 INC REG endif endif ENDM code segment start: ADDNUM AX,1 ; ADDNUM BX,3 mov ax,4c00h int 21H code ends end start
条件汇编伪指令 伪指令 IFB 和 IFNB 伪指令 IFB 和 IFBN 伪指令 IFB 一般用在宏定义内,格式如下:
IFB <参数>
如果在宏调用时没有使用实参来代替该形参,那么条件满足。注意,参数应该用尖括号起。
伪指令 IFNB 一般使用在宏定义,格式内,格式如下:
IFNB <参数>
如果在宏调用时使用实参来代替该形参,那么条件满足。注意,参数应该用尖括号括起。
例如:如下定义的宏 PRINT,若指定显示信息时,则显示,否则显示缺省信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ;程序名:T7-18-1.asm ;功能:演示 宏中使用条件汇编 assume cs:code,ds:data PRINT MACRO MSG ifb <MSG> MOV SI,OFFSET DEFAULTMSG ;如果没有实参,输出 DEFAULTMSG else MOV SI,OFFSET MSG ;如果有实参,输出 MSG 参数信息 endif CALL SHOWIT ENDM data segment DEFAULTMSG db 'Hello World!','$' MESS1 db 'I LOVE MASM!','$' data ends code segment start: mov ax,data mov ds,ax ; PRINT MESS1 mov ax,4c00h int 21H ;------------------------------- SHOWIT PROC mov dx,si mov ah,9 int 21h ret SHOWIT ENDP ;------ code ends end start
伪指令 IFIDN 和 IFDIF 伪指令 IFIDN 一般使用在宏定义内,格式如下:
IFIDN <参数1>,<参数2> IDIDNI <参数1>,<参数2>
如果 字符串参数1 与 字符串参数2 相等,则条件满足。参数1 或 参数2 可以是宏定义中的形参,如果是形参,会由之前相应的实参代替。字符串是按字符逐个比较的,格式一对大小写有区别,格式二忽略大小写区别。注意,参数应用尖括号括起。
伪指令 IFDIF 一般使用在宏定义内,格式如下:
IFDIF <参数1>,<参数2> IFDIFI <参数1>,<参数2>
如果字符串1 与 字符串2 不等,则条件满足。其他说明同上。 例如:定义的宏 RDWR 的第二个参数就决定了读写方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 ;程序名:T7-18-2.asm ;功能:演示 宏中使用条件汇编 assume cs:code,ds:data RDWR MACRO BUFF,RWMODE LEA DX,BUFF ifidni <RWMODE>,<READ> CALL READIT endif ifidni <RWMODE>,<WRITE> CALL WRITEIT endif ENDM data segment BUFFER dw 0102h data ends code segment start: mov ax,data mov ds,ax ; RDWR BUFFER,READ ; RDWR BUFFER,WRITE RDWR BUFFER,READ mov ax,4c00h int 21H ;------------------------------- READIT PROC mov si,dx add dx,word ptr [si] add dx,3030h xchg dh,dl mov ah,2 int 21h ; xchg dh,dl int 21h ret READIT ENDP ;-------------------------------- WRITEIT PROC mov si,dx mov word ptr [si],0304h ret WRITEIT ENDP code ends end start
例如:如下定义的宏 GETCH 就有加强能力
1 2 3 4 5 6 7 8 9 GETCH MACRO CHAR MOV AH,1 INT 21H ifnb <CHAR> ;如果有参数,执行下面的语句。 ifdifi <CHAR>,<AL> ;如果有参数,就把AL的值传给形参 CHAR MOV CHAR,AL endif endif ENDM
源程序的结合 为了方便编辑源程序和对程序进行修改或维护,汇编程序允许把源程序存放在多个文本文件中,在汇编时结合到一起,同时参加汇编。
源程序的结合 存放在若干文本文件中的源程序的结合是利用伪指令 INCLUDE 完成的。它的一般格式如下:
INCLUDE 文件名
伪指令 INCLUDE 表示汇编程序将指定的文本文件从本行起加入汇编,直到该文本文件的最后一行汇编完成后,继续汇编随后的语句。
文件名可带有盘符和路径,采用 DOS 有关规则表示。
若文件名没有盘符或路径,则首先在汇编命令行参数 /I 所指定的目录中寻找该文件,然后再到当前目录中寻找该文件。
对于 MASM 而言,最后还会由环境变量 INCLUDE 所指定的目录中寻找该文件。对于 TAMS 而言,若文件名没有扩展名,则默认为扩展名 ASM
下列程序的功能是:接收一个字符串,然后按小写和大写形式重新显示字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 ;程序名:T7-19.asm ;功能:接收一个字符串,然后按小写和大写形式重新显示字符串。 assume cs:code,ds:data INCLUDE DATA.ASM code segment start: mov ax,data mov ds,ax ; cld mov dx,offset MESS1 ;显示提示信息 mov ah,9 int 21H ; mov ah,10 ;接收字符串 mov dx,offset BUFFER int 21H ; call NEWLINE ;另起一行 mov bl,BUFFER+1 xor bh,bh mov BUFFER[BX+2],0 mov si,offset STRBEG call STRLWR ;转换成小写字符串 mov SI,offset STRBEG call DISPMESS ;显示信息 call NEWLINE ;换行 ; mov si,offset STRBEG call STRUPR ;转换成大写字符串 mov si,offset STRBEG call DISPMESS call NEWLINE EXIT: mov ax,4c00h int 21H ;-------------------------------------------------------- NEWLINE PROC ; 功能:形成回车和换行(光标移到写一行首) ; 入口参数:无 ; 出口参数:无 ; 说 明:通过显示回车符形成回车,通过显示换行符形成换行 ;--------------------------------------------------------- push ax push dx mov dl,0dh mov ah,2 int 21h mov dl,0ah mov ah,2 int 21h pop dx pop ax ret NEWLINE endp ;------------------------------- DISPMESS PROC ;功能:显示以0结尾的字符串 ;入口参数:SI=字符串首地址偏移 DISPME1: LODSB or al,al jz DISPME2 mov dl,al mov ah,2 int 21H jmp DISPME1 DISPME2: ret DISPMESS ENDP INCLUDE STRING.ASM ;结合含子程序的文件 STRING.ASM code ends end start
文本 DATA.ASM 内容如下:
1 2 3 4 5 6 7 8 ;文件名:DATA.ASM ;内容:程序 T7-19.asm 的一部分 STRLEN = 128 DATA SEGMENT BUFFER DB STRLEN,0,STRLEN DUP(0) STRBEG = BUFFER+2 MESS1 db 'Please Enter string: ','$' DATA ENDS
文本文件 STRING.ASM 内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ;文件名:STRING.ASM ;内容:程序 T7-19.asm 的一部分 STRLWR PROC ;子程序名:STRLWR ;功能:把字符串转换为小写 ;入口参数:SI=字符串起始地址偏移 jmp STRLWR2 STRLWR1: sub al,'A' cmp al,'Z'-'A' ja STRLWR2 add al,'a' mov [si-01],al STRLWR2: LODSB and al,al jnz STRLWR1 ret STRLWR ENDP STRUPR PROC ;子程序名:STRUPR ;功能:把字符串转换成大写 ;入口参数:SI-=字符串起始地址偏移 jmp STRUPR2 STRUPR1: sub al,'a' cmp al,'z'-'a' ja STRUPR2 add al,'A' mov [si-01],al STRUPR2: LODSB and al,al jnz STRUPR1 ret STRUPR ENDP
宏库的使用 通常程序员会把一组有价值和经常使用的宏定义集中存放在一个文本文件中,这样的文本文件称为宏库。有了宏库之后,只要在源程序首使用伪指令 INCLUDE,就能方便地调用宏库了。
例如:建立宏库 DOSBIO.MAC 内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 ;接受一个字符串 GETSTR MACRO MBUFF MOV DX,MBUFF MOV AH,10 INT 21H ENDM ;显示一个字符串 DISPSTR MACRO MBUFF MOV DX,MBUFF MOV AH,9 INT 21H ENDM ;取得一个字符 GETCH MACRO CHAR MOV AH,1 INT 21H ifnb <CHAR> ;如果有参数,执行下面的语句。 ifdifi <CHAR>,<AL> ;如果有参数,就把AL的值传给形参 CHAR MOV CHAR,AL endif endif ENDM ;显示一个字符 ECHOCH MACRO CHAR IFNB <CHAR> IFDIFI <CHAR>,<DL> MOV DL,CHAR ENDIF ENDIF MOV AH,2 INT 21H ENDM
源程序代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 ;程序名:T7-19-1.asm ;功能:演示宏库的使用 assume cs:code,ds:data INCLUDE DOSBIO.MAC ;结合宏库 DOSBIO.MAC ; INCLUDE DATA.ASM ;含数据部分的文件 DATA.ASM code segment start: mov ax,data mov ds,ax ; cld DISPSTR <OFFSET MESS1> ;调用宏 DISPSTR GETSTR <OFFSET BUFFER> ;调用宏 GETSTR call NEWLINE ; mov bl,BUFFER+1 xor bh,bh mov BUFFER[BX+2],0 mov si,offset STRBEG call STRLWR ;转换成小写字符串 mov SI,offset STRBEG call DISPMESS ;显示信息 call NEWLINE ;换行 ; mov si,offset STRBEG call STRUPR ;转换成大写字符串 mov si,offset STRBEG call DISPMESS call NEWLINE EXIT: mov ax,4c00h int 21H ;-------------------------------------------------------- NEWLINE PROC ; 功能:形成回车和换行(光标移到写一行首) ; 入口参数:无 ; 出口参数:无 ; 说 明:通过显示回车符形成回车,通过显示换行符形成换行 ;--------------------------------------------------------- push ax push dx mov dl,0dh mov ah,2 int 21h mov dl,0ah mov ah,2 int 21h pop dx pop ax ret NEWLINE endp ;------------------------------- DISPMESS PROC ;功能:显示以0结尾的字符串 ;入口参数:SI=字符串首地址偏移 DISPME1: LODSB or al,al jz DISPME2 mov dl,al mov ah,2 int 21H jmp DISPME1 DISPME2: ret DISPMESS ENDP INCLUDE STRING.ASM ;结合含子程序的文件 STRING.ASM code ends end start