宏汇编语言程序设计
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第3章宏汇编语言程序设计
3.1 教学要求
·熟悉汇编语言程序的结构及其语句的结构。
·熟悉伪指令及其功能。
·掌握汇编语言结构化程序设计方法,即顺序结构、分支结构和循环结构的设计。
·熟悉过程(子程序)和宏指令的定义及使用规则。
·熟悉DOS调用功能及其规则。
3.2 内容提要
3.2.1 汇编语言的语句结构
汇编语言程序是由语句组成的。
语句的格式如下
[名称[:]] 操作助记符操作数[,操作数][;注释]
汇编语言语句的分成包含名称、操作助记符、操作数、注释四个域。
语句格式“[ ]”中的内容为可选项,根据实际情况而使用。
1. 操作助记符域
操作助记符域是指令中唯一不可缺少的部分,由指令、伪指令或宏指令构成。
(1)指令
指令是计算机可以执行的基本操作,汇编时可以转换为机器代码。
操作助记符为指令的语句称为指令语句,每条指令语句在执行时都对应一条机器指令。
例如MOV、ADD、SUB 等皆为指令。
(2)伪指令
伪指令是在汇编语言源程序中使用的指令,用来定义段、定义数据、分配存储器、指示程序结束等。
伪指令语句只在汇编语言源程序中使用,不会转换成机器代码,机器不能执行。
例如,SEGMENT、DB、END等皆是伪指令。
(3)宏指令
宏指令是由用户自己定义的指令。
2. 名称域
名称包括标号和变量。
名称在汇编语言源程序中定义和使用,名称应以字母开头,后跟若干字母、数字或特殊符号(“_”、“.”、“?”、“$”、“@”)组成,符号个数不超过31个。
(1)标号
标号在代码段中定义和使用,标号后面需要加“:”号,例如“START:”。
标号用来表示代码段中某条语句的地址,因此也称为代码的符号地址。
利用标号可以方便实现程序的转
移和循环的控制。
标号有段属性、偏移属性和类型属性。
段属性代表定义标号的段地址;偏移属性代表标号的偏移地址;类型属性代表标号的使用范围,NEAR类型属性的标号只能在段内使用,而FAR类型属性的标号可以在不同段之间使用。
(2)变量
变量在除代码段以外的其他段(DS,SS,ES)定义,后不跟“:”号。
变量用来表示数据的地址,因此也称为数据的符号地址。
变量定义格式为
变量名变量属性伪指令表达式
变量属性伪指令包括DB(定义变量属性为字节)、DW(定义变量属性为字)、DD(定义变量属性为双字)、DQ(定义变量属性为四字)、DT(定义变量属性为五字)。
表达式包括
①一个或多个常量或表达式,中间以“,”号分隔。
例如
DA T1 DB 20H,30H,40H;
②用单引号括起来的字符或字符串。
例如
DA T2 DB ‟A‟,‟1234‟;
用DB伪指令定义字符串时,字符串包含的字符个数不受限制,而用DW、DD、DQ和DT伪指令定义字符串时,字符个数不允许超过2个。
③一个“?”号,表示数据未定,常用于预留存储空间。
例如
DA T3 DW ?;
④重复方式。
重复方式的格式为
重复次数DUP (表达式);
例如,用DA T4变量名预留256个字空间的定义语句为
DA T4 DW 256 DUP(?)。
变量定义的目的是确定变量的属性和安排数据的存储单元。
变量的属性包括段属性SEG (定义变量的段地址)、偏移属性OFFSET(定义变量的偏移地址)、类型属性TYPE(表示一个变量数据所占的字节数)、长度属性LENGTH(表示定义变量的重复次数,在DUP重复方式下同一变量名的长度属性为重复次数,非重复方式定义的变量长度均为1)、大小属性SIZE(SIZE=TYPE×LENGTH)。
变量的类型属性包括字节类型BYTE(占1字节)、字类型WORD(占2字节)、双字类型DWORD(占4字节)、四字类型(占8字节)和五字类型(占10字节)。
例如:DAT1 DW 10 DUP(10H),TYPE DAT1的值为2。
3. 操作数域
操作数是参与操作的数据或数据所在的地址。
操作数包括常数、变量和表达式。
(1)常数
常数有数值常数和用单引号括起来的字符常数。
数值常数可用二进制数(后缀加B)、十进制数(后缀加D或不加后缀)、十六进制数(后缀加H)和八进制数(后缀加Q)。
例如
MOV AL,‟A‟;
MOV AL,20H;
MOV AL,10001001B;
都是用常数作为操作数的程序语句。
(2)变量
变量必须在定义后才能使用。
由于变量是符号地址,因此变量代表的操作数表示数据所
在的地址。
例如,在程序语句“MOV AL,DAT1”中,是用变量DAT1来表示操作数的。
(3)表达式
表达式是用运算操作符将常量、变量连接起来的式子。
①算术运算操作符。
算术运算操作符有+(加)、-(减)、*(乘)、/(除)、MOD (求余)、SHL(左移)、SHR(右移)。
②逻辑操作符。
逻辑操作符有AND(与)、OR(或)、NOT(非)、XOR(异或)。
③关系操作符有EQ(等于)、NE(不等于)、LT(小于)、GT(大于)、LE(小于等于)、GE(大于等于)。
关系表达式的结果为逻辑值。
当关系成立时,结果为全“1”,即为“真”;当关系不成立时,结果为全“0”,即为“假”。
例如,设V AR1=5,V AR2=6,则执行
MOV AL,V AR1 GT V AR2
的结果为AL=00H(假)。
④数值回送操作符。
数值回送操作符有SEG(取段地址值)、OFFSET(取偏移地址值)、TYPE(取类型值)、LENGTH(取长度值)、SIZE(取大小值)。
⑤修改属性操作符PTR。
PTR操作符的使用格式为
类型PTR 表达式
其中,类型包括BYTE(字节)、WORD(字)、DWORD(双字)、NEAR(近程)、FAR (远程)。
表达式包括变量、标号和地址表达式。
PTR操作符用于临时修改语句中操作数的属性,完成同类型数据的匹配操作。
例如,数据段定义的变量DA T1为字节类型,则语句
MOV AX,DAT1;
出现类型不匹配错误。
利用PTR操作符可以修改DAT1的类型来修正语句中的错误。
修改后的语句为
MOV AX,WORD PTR DA T1。
⑥其他运算符。
其他运算符有THIS(指定类型/距离属性运算符)、HIGH(分离高字节运算符)、LOW(分离低字节运算符)。
4. 注释域
注释域以“;”号分隔,用来对程序或语句的功能加以注释。
汇编时对此部分内容不作任何加工,仅作为源程序的说明,便于理解程序。
注释部分虽然不属于程序本身的内容,但良好的注释是程序可读性的保障。
3.2.2 伪指令
伪指令是在汇编语言源程序中使用的指令,用于定义不同的程序段、定义过程、分配存储空间、指定程序结束处等。
汇编时伪指令不能转换为代码,因此被称为伪指令。
1. 段定义伪指令
一个汇编语言程序根据需要被划分为功能不同的几个段,一般至少应该有一个代码段和一个数据段,根据需要再增加堆栈段和附加数据段。
程序执行时,不同的段被加载到不同的存储地址,因此编写程序时要对不同的段分别做出定义。
段定义格式为
段名SEGMENT [定位类型][组合类型][类别]
定义内容
段名ENDS
其中SEGMENT和ENDS是段定义伪指令助记符,必须成对出现,它们前面有相同的
段名。
段定义语句中的[定位类型]、[组合类型]、和[类别]是3个可选项,定位类型用于确定各段起始地址的规则;组合类型和类别用于模块化设计程序汇编时,告知连接程序LINK各模块之间的通讯方式和各段之间的组合方式,从而把各模块正确地连接在一起。
2. 过程定义伪指令
过程也称为子程序,过程定义格式为
过程名PROC [类型]
定义内容
过程名ENDP
其中PROC和ENDP是过程定义伪指令助记符,必须成对出现,它们前面有相同的过程名。
定义语句中的[类型]是可选项,用于指出过程是属于NEAR类型还是FAR类型,若没有类型选项则默认为NEAR类型。
3. 假定伪指令ASSUME
假定伪指令ASSUME用于为各定义的段指定使用的寄存器,便于汇编程序根据给定的段地址和段内的偏移地址计算出各数据和程序代码的物理地址。
假定伪指令ASSUME的格式为
ASSUME 段寄存器名:段名[,段寄存器名:段名,…]
例如,SSEG、DSEG和CSEG是已经定义的堆栈段、数据段和代码段的段名,用ASSUME 伪指令假定的语句为
ASSUME CS:DSEG,DS:DSEG,SS:SSEG;
ASSUME伪指令只是将段寄存器与相应段的关系通知了汇编程序,但没有把段首地址置入段寄存器中,CS段寄存器的值是由系统自动置入,而DS、SS和ES的值需要在程序中用指令置入。
例如,DSEG和CSEG是已经定义的数据段和代码段,则它们的初始值置入的语句如下
MOV AX,DSEG; 取数据段的段地址
MOV DS,AX; 装入数据段寄存器
MOV AX,SSEG; 取堆栈段的段地址
MOV SS,AX; 装入堆栈段寄存器
4. 汇编地址计数器伪指令$
汇编地址计数器伪指令用符号$表示,它用来记录正在汇编的程序语句的偏移地址,每安排1字节的数据或指令代码,$的值加1。
$中的初值可以用定位ORG伪指令设置,例如“ORG 0020H”,则$的初值被设置为0200H。
若不使用ORG伪指令定位,则$的初值伪0。
5. 源程序结束伪指令END
汇编语言源程序结束时要使用END伪指令,其格式伪
END [表达式]
其中可选项[表达式]必须是第一条可执行的程序语句的地址,可以是标号或者是过程名。
例如,“END START”。
如果不带表达式,则该程序不能单独执行,只是供其他程序调用的子模块。
3.2.3 汇编语言程序的格式
汇编语言源程序可以按照主程序格式和过程格式来编写。
1. 主程序编写格式
汇编语言源程序一般由堆栈段、数据段和代码段组成,根据需要还包括附加段(例如使用字符串操作指令)。
汇编语言的主程序编写格式如下
STACK SEGMENT STACK; 定义堆栈段
DW 256 DUP(?);
TOP LABEL WORD; 设置堆栈指针变量
STACK ENDS;
DA TA SEGMENT; 定义数据段
…;
DA TA ENDS;
CODE SEGMENT; 定义代码段
ASSUME CS:CODE,DS:DATA,SS:STACK;
START: MOV AX,DA TA;
MOV DS,AX; 设置数据段地址
MOV AX,STACK;
MOV SS,AX; 设置堆栈段地址
MOV SP,OFFSET TOP; 设置堆栈指针
…; 完成算法的程序段
DONE: MOV AH,4CH;
INT 21H; 程序结束,返回DOS操作系统
CODE ENDS;
END START; 源程序结束
这是一个以主程序格式编写的汇编语言源程序的标准框架,编程时只需要在省略号的部分填写相应的数据和程序代码即可。
2. 过程编写格式
汇编语言程序的过程编写格式一般也包括堆栈段、数据段和代码段,其格式如下
STACK SEGMENT STACK; 定义堆栈段
DW 256 DUP(?);
TOP LABEL WORD; 设置堆栈指针变量
STACK ENDS;
DA TA SEGMENT; 定义数据段
…;
DA TA ENDS;
CODE SEGMENT; 定义代码段
ASSUME CS:CODE,DS:DATA,SS:STACK;
START PROC FAR; 定义过程
PUSH DS; 保护PSP段地址
XOR AX,AX;
PUSH AX; 保护PSP的偏移地址
MOV AX,DA TA;
MOV DS,AX; 设置数据段地址
…; 完成算法的程序段
RET;
START ENDP; 过程结束
CODE ENDS;
END START; 源程序结束
汇编语言源程序都要经过汇编和连接才能生成可执行文件exe ,在DOS 状态执行。
在执行exe 文件时,DOS 会在装载exe 文件的前部建立一个256字节的程序段前缀PSP (program segment prefix ),存放连接时使用的信息以及程序参数,在其首地址安放了一条软中断返回指令INT 20H 。
程序执行时CS 指向exe 程序的代码段,SS 指向堆栈段,而DS 并不指向用户程序的数据段,是指向PSP 。
因此,在程序的开始处首先用“PUSH DS ”语句保护数据段DS 的段地址,即保护PSP 的段起始地址,用“PUSH AX ”语句保护PSP 的偏移地址(偏移地址为0000H ),然后用语句设置用户数据段的段地址。
当程序结束用RET 语句返回时,返回的地址是PSP 的第一条地址,即执行“INT 20H ”指令,返回DOS 操作系统。
返回DOS 的指令有两种,一种是4CH 功能执行软中断指令“INT 21H ”(见主程序编写格式),执行时无论在任何状态下均可结束程序并返回到DOS 操作状态。
另一种是“INT 20H ”指令,执行时是返回调用程序。
在用过程格式编写源程序时,执行exe 文件即为被DOS 调用,因此可以用“INT 20H ”指令返回。
3.2.4 汇编语言程序的结构
在汇编语言程序中,常见的结构形式包括顺序结构、分支结构、循环结构和子程序结构。
掌握这几种程序结构程序的设计是汇编语言程序设计的基础。
1. 顺序程序结构
顺序程序的结构如图3.1所示,这是一种最简单的结构,程序全部由执行框构成,程序的执行顺序与程序的排列顺序完全一致。
2. 分支程序结构
分支程序结构如图3.2所示。
在分支程序中至少包含一个判断框,判断框有一个入口和两个出口。
判断框是对某个条件进行判断,条件有“真”(成立Yes )和“假”(不成立No )两种结果,当条件为“真”时则用转移指令转移到“出口1”执行语句,否则(即条件为“假”时)顺序执行(转移指令下面的)“出口2”语句。
实现转移的指令包括无条件转移指令JMP 和条件转移指令。
3. 循环程序结构
图3.1 顺序程序结构
图3.2 分支程序结构
循环程序结构如图3.3所示。
循环程序一般包含循环准备、循环执行、循环修改和循环判断4个部分。
循环准备是为循环设置循环次数和循环操作的地址指针;循环操作是循环重复执行的部分,也称为循环体;循环修改用于修改循环次数和地址指针;循环判断用来判断循环是否结束,是则结束循环,否则转移到循环重复部分继续执行循环操作。
循环程序有两种结构,一种是“先执行后判断”,如图 3.3(a)所示,这种结构的循环是先执行一次循环体,然后判断循环是否结束。
另一种循环结构是“先判断后执行”,如图 3.3(b)所示,这种结构是先判断是否满足循环条件,是则执行循环体,否则退出循环。
4. 子程序结构
子程序是一个可供主程序或其他子程序调用的独立的程序模块。
主程序用CALL 指令调用子程序。
程序执行CALL 指令时,转到CALL 指令指出的子程序处执行。
子程序用RET 指令返回主程序,继续执行CALL 指令的下一条指令。
由于程序的流程都是由段寄存器CS:和指令指针寄存器IP 控制的,因此在转向子程序前,CALL 指令自动先将下一条指令的CS 和IP 值保存进堆栈(CS 先进栈,IP 后进栈),然后再将子程序的地址送给CS 和IP 寄存器,实现程序的转移。
RET 指令是这个过程的逆过程,它将堆栈中的IP 和CS 分别出栈(IP 先出栈,CS 后出栈),实现返回主程序的功能。
3.2.5 宏指令
宏指令是由一个具有独立功能的程序段构成的用户自定义指令,供汇编语言编写程序时使用。
宏指令需要经过定义(即宏定义)后才能使用,宏定义要在源程序的前部完成,宏定义的格式如下
宏指令名 MACRO [形参表]
宏体
ENDM
在宏定义中,MACRO 和ENDM 是宏定义助记符,宏指令名由用户命名,它可以与指令助记符或者伪指令同名,当宏指令与指令符或者伪指令同名时,这些指令或者伪指令将失去它们原来的功能。
宏定义中的形参表是一些临时变量的名称,类似指令格式中的操作数,多个形参之间用“,”号分隔。
宏定义中具有独立功能的程序段称为宏体。
在汇编语言源程序中使用宏指令称为宏调用,宏调用的格式为
宏指令名 [实参表]
在宏调用时,用实参表代替宏定义中形参表,实参的个数、类型和顺序要与形参保持一致,多个实参之间用“,”号分隔。
图3.3 循环程序结构
宏指令可以简化源程序的编写,但不简化目标程序,宏指令在汇编时将宏体插入宏调用处,这个过程称为宏展开。
3.2.6 DOS系统功能调用
IBM PC微型计算机系统为汇编用户提供了两个程序接口,即DOS系统功能调用和基本输入输出系统BIOS(Basic input output system)。
DOS系统功能调用和BIOS由一批供中断调用的中断服务子程序构成,用软中断指令“INT N”调用。
调用的格式为MOV AH,N1;
INT N2;
其中N1是调用的功能号,必须放在AH寄存器中,不同的功能号调用不同的中断服务程序,实现不同的功能;N2是类型码,不同的类型码实现不同类型的输入输出的调用。
DOS系统功能调用的类型码为21H,它提供了100个供显示、键盘、磁盘、文件管理和串并口等方面操作调用的子程序。
每个子程序有一个编号,称为功能号,功能号从00H~63H。
另外,中断调用时可能需要根据不同的使用情况设置不同的初始环境,这些环境称为入口参数,调用后返回的参数称为出口参数。
与汇编语言编程密切相关的系统功能调用如表3.1所列。
表3.1 汇编程序常用的DOS系统功能调用
3.3 例题解析
【例3.1】已知程序段如下
ORG 100H
ARY DW 3,$+4,5,6
CNT EQU $-ARY
DW 7,CNT,8
则执行MOV AX,ARY+2和MOV BX,ARY+10后,AX= ,BX= 。
解:本例的目的是为了帮助读者加深对汇编地址计数器伪指令$的理解。
在程序段的开始,$中的初值被定位ORG伪指令设置为0100H,指出ARY变量的第1个字数据“3”的偏移地址,当字数据“3”安排结束后,$=0100H+2=0102H,因此ARY安排的第2个字数
据是$+4=0102H+4=0106H,即为变量ARY的第2字节开始的数据。
当变量ARY的4个字数据安排结束时,$的值为0100H+8=0108H,因此常数CNT=$-ARY=0108H-0000H=0108H (ARY的偏移地址是0000H),即为ARY的第10个字节开始的数据。
根据上述分析,本例题的结果为
AX=[ARY+2]=0106H
BX=[ARY+10]=0108H
【例3.2】已知定义的存储区如下
V AL DB 93 DUP(5,2 DUP(2 DUP(1,2 DUP(3)),4))
写出该存储区的前10个字节单元数据。
解:此题可以用对DUP定义逐层展开的方法来求解。
从里到外逐个展开,原定义表达式相当于以下各式
V AL DB 93 DUP(5,2 DUP(2 DUP(1,3,3),4))
V AL DB 93 DUP(5,2 DUP(1,3,3,1,3,3,4))
V AL DB 93 DUP(5, 1,3,3,1,3,3,4,1,3,3,1,3,3,4)
至此不难写出其前10个字节单元的内容为
5, 1,3,3,1,3,3,4,1,3
【例3.3】编写程序段完成将AL寄存器的8位数据按位顺序倒置(即最高位变成最低位,最低位变成最高位)。
解:这里所说的“程序段”是指完成指定功能的代码,而不需写出整个汇编语言源程序结构。
本题可利用带进位的位移指令实现。
将AL中的各位逐位移到进位标志CF中,再逐位回送。
实现本题功能的程序段如下
MOV BL,AL; 将AL的值送BL保存
MOV CX,8; 循环8次
LL: ROL BL,1; BL依次左移,最高位送CF
RCR AL,1; AL依次右移,最高位用CF填充
LOOP LL;
【例3.4】用查表法求0~9之间的十进制数的平方值。
解:本题解析顺序程序的编程方法和字节交换指令XLAT的使用方法。
用查表法求0~9之间的十进制数的平方值的解题思路为:首先在数据段设置一个0~9之间的十进制数的平方表TABLE,按顺序存放0~9的平方值,如图3.4所示;然后用DOS系统功能调用指令从键盘输入一位十进制数,由于输入的是十进制数字符(即数的ASCII码),因此需要把字符ASCII码的高4位去掉,得到实际十进制数数值,再执行XLAL指令,将查到的相应数值的平方值交换到AL寄存器中。
以主程序格式编写的完整程序如下
STACK SEGMENT STACK; 定义堆栈段
DW 256 DUP(?);
TOP LABEL WORD; 设置堆栈指针变量STACK ENDS;
DA TA SEGMENT; 定义数据段TABLE DB 0,1,4,9,16,25,36,49,64,81;
DAT1 DB ?;
DA TA ENDS;
CODE SEGMENT; 定义代码段
ASSUME CS:CODE,DS:DATA,SS:STACK; START: MOV AX,DA TA;
图3.4 平方表
MOV DS,AX; 设置数据段地址
MOV AX,STACK;
MOV SS,AX; 设置堆栈段地址
MOV SP,OFFSET TOP; 设置堆栈指针
MOV BX,OFFSET TABLE;
MOV AH,01H;
INT 21H; 由键盘输入字符到AL
AND AL,0FH; 去掉高4位
XLAT;
MOV DAT1,AL; 存结果
MOV AH,4CH;
INT 21H;
CODE ENDS;
END START;
在程序执行时若从键盘键入…5‟,结果DA T1=25。
【例3.5】检查N单元的数据是否为1位十六进制数,是则将其转换为ASCII码,否则置为0FFH。
解:本题解析分支结构程序的编程方法。
在程序设计时需要判断N单元的是否1位十六进制数,不是1位十六进制数的情况包括小于0(即80H~FFH)和大于15(即10H~7FH)的数。
属于0~9范围的十六进制数,将其值加上30H即可得到其对应的ASCII码。
例如,数值9的ASCII码为9+30H=39H。
属于A~F的十六进制数,将其值加上37H即可得到其对应的ASCII码。
例如,数值A的ASCII码为A+30H=41H。
表示本题算法的流程图如图3.5所示,以主程序格式编写的完整程序如下
STACK SEGMENT STACK;
DW 256 DUP(?);
TOP LABEL WORD;
STACK ENDS;
DA TA SEGMENT;
N DB 08H;
DA T1 DB ?;
DA TA ENDS;
CODE SEGMENT;
ASSUME CS:CODE,DS:DA TA,SS:STACK; START: MOV AX,DATA;
MOV DS,AX;
MOV AX,STACK;
MOV SS,AX;
MOV SP,OFFSET TOP;
MOV AL,N;
CMP AL,0;
JS DONE1;
CMP AL,09H;
JLE NEXT1;
CMP AL,0FH;
图3.5 例3.5流程图
JG DONE1; ADD AL,07H; NEXT1: ADD AL,30H; NEXT2: MOV N,AL; JMP DONE2; DONE1: MOV AL,0FFH; JMP NEXT2; DONE2: MOV AH,4CH; INT 21H; CODE ENDS; END START;
【例3.6】统计数组ARRAY 中负数的个数,数组的元素个数存放在DAT1中,将统计结果存于DAT2中。
解:本题解析循环结构程序的编程方法,表示其算法的流程图如图3.6所示,以主程序格式编写的完整程序如下
STACK SEGMENT STACK;
DW 256 DUP (?);
TOP LABEL WORD;
STACK ENDS;
DA TA SEGMENT;
ARRAY DB 1,2,-5,78,63,-55,-43,74,-22;
DA T1 DB $-ARRAY;
DA T2 DB 00H;
DA TA ENDS;
CODE SEGMENT;
ASSUME CS:CODE,DS:DA TA,SS:STACK; START: MOV AX,DATA; MOV DS,AX;
MOV AX,STACK;
MOV SS,AX; MOV SP,OFFSET TOP;
MOV CX,DAT1; MOV SI,OFFSET ARAY;
LOP: MOV AL,[SI]; CMP AL,0; JNS NEXT; INC DAT2; NEXT: INC SI; LOOP LOP; MOV AH,4CH; INT 21H; CODE ENDS; END START;
【例3.7】统计TW 字数据中“1”的个数,并将统计结果存于TWS 单元中。
图3.6 例3.6流程图
解:本题解析子程序的设计方法。
在本例程序设计中,用子程序SUB1实现对一个字节中“1”的个数的统计,被统计的字节作为入口参数存放在AL 寄存器中;统计结果存放在BX 寄存器中,作为出口参数返回。
表示本例算法的流程图如图3.7所示。
采用远程调用方式编写的源程序如下 STACK SEGMENT STACK; DW 256 DUP (?); TOP LABEL WORD; STACK ENDS;
DA TA SEGMENT; TW DW 1234H; TWS DW 00H; DA TA ENDS;
CODE SEGMENT;
ASSUME CS:CODE,DS:DA TA,SS:STACK; START: MOV AX,DATA; MOV DS,AX; MOV AX,STACK; MOV SS,AX; MOV SP,OFFSET TOP;
MOV AX,TW;
CALL FAR PTR SUB1; PUSH BX; MOV AL,AH;
CALL FAR PTR SUB1;
图3.7 例3.7流程图
POP AX;
ADD AX,BX;
MOV TWS,AX;
MOV AH,4CH;
INT 21H;
CODE ENDS;
END START;
CODE1 SEGMENT;
ASSUME CS:CODE1;
SUB1 PROC FAR;
MOV CX,08H;
MOV BX,0;
LOP: SHL AL,1;
JNC NEXT;
INC BX;
NEXT: LOOP LOP;
RET;
SUB1 ENDP;
CODE1 ENDS;
END START;
【例3.8】用字符串操作指令比较BUFF1和BUFF2两个字符串是否完全相同,相同则将COMP1单元置为00H,不同则置位0FFH。
解:本题解析字符串操作指令的使用方法,表示其算法的流程图如图3.8所示。
在程序设计中使用了串比较指令CMPSB,该指令要求源操作数存放在DS数据段,并用源变址指针SI指出操作数的偏移地址;要求目的操作数在ES附加段中,并用目的变址寄存器DI指出其操作数的偏移地址。
因此,在源程序中不仅要设置数据段,还要设置附加段。
在本例中,是将DS和ES两个段共有一个数据区。
以主程序格式编写的完整程序如下STACK SEGMENT STACK;
DW 256 DUP(?);
TOP LABEL WORD;
STACK ENDS;
DA TA SEGMENT;
BUFF1 DB 64H DUP (?);
CNT DW $-BUFF1; 设置字符串长度
BUFF2 DB 64H DUP (?);
COMP1 DB ?;
DA TA ENDS;
CODE SEGMENT;
START: MOV AX,DA TA;
MOV DS,AX;
MOV ES,AX;
MOV AX,STACK;
MOV SS,AX;
MOV SP,OFFSET TOP;
MOV CX,CNT;
MOV SI,OFFSET BUFF1;
MOV DI,OFFSET BUFF2;
CLD; 置方向标志DF=0
REPE CMPSB;
JCXZ NEXT;
MOV COMP1,0FFH;
JMP DONE;
NEXT: MOV COMP1,00H;
DONE: MOV AH,4CH;
INT 21H;
CODE ENDS;
END START;
【例3.9】编写程序从键盘输入一个字符串,然后输出回显这个字符串。
解:本题解析DOS系统功能调用的使用方法。
在程序设计中使用了0AH号系统功能调用指令输入字符串,该指令使用前要求在DS数据段设置一个输入缓冲区(本例使用BUFF 作为输入缓冲区名),并在缓冲区的第1个字节存放缓冲区的总长,在第2字节留输入字符串的实长。
字符串操作执行结束后,用09H号系统功能调用指令输出这个字符串,该指令使用前要求在输出字符串的最后加结束字符‟$‟。
下面是以主程序格式编写的完整程序。
STACK SEGMENT STACK
DW 256 DUP(?)
TOP LABEL WORD
STACK ENDS
DA TA SEGMENT
BUFF DB 30H,00H,30H DUP(?)
DA TA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DA TA,SS:STACK
START:MOV AX,DATA;
MOV DS,AX;
MOV AX,STACK;
MOV SS,AX;
MOV SP,OFFSET TOP;
LEA DX,BUFF;
MOV AH,0AH;
INT 21H; 输入字符串
MOV BL,[BUFF+1]; 取输入字符串的实长
MOV BH,0;
MOV AL,‟$‟;
MOV BUFF[BX+2],AL; 在字符串末尾添加‟$‟结束符
LEA DX,BUF+2; 取显示字符串首址
MOV AH,09H;
INT 21H; 显示字符串
MOV AH,4CH;
INT 21H;
CODE ENDS;
END START;
【例3.10】编写密码输入程序。
解:本题解析宏指令的使用方法。
解题的算法步骤如下
①提示“Please Input Your Password.”
②用输入字符不回显方式一位一位输入密码,每输入1位密位后显示一个“*”号;密码输入由子程序SUB1完成。
③比较输入的密码。
密码比较由子程序SUB2完成,子程序的出口参数为AL,当AL=00H时表示输入的密码正确,AL=0FFH时表示输入密码错误。
④若密码正确,转操作程序。
操作程序以显示“OK!”并结束作为模块示例。
⑤若密码错误,显示“Wrong!”重新输入密码。
⑥密码输入最多3次,3次均为错误时显示“NO!”退出。
表示本题算法的主程序和子程序SUBI、SUB2的流程图分别如图3.9、3.10和3.11所示。
在程序编写中,用MINC宏指令完成输入字符不回显功能,用MOUTS宏指令完成字符串输出功能,这些宏指令的定义均放在源程序的前部。
另外程序的子程序采用NEAR(近程)属性,因此将主程序与子程序安排在同一个代码段。