一、概述
单一的指令其功能比较简单,要解决一个实际问题总是需要一组指令按一定的思路有 机地组合起来才能完成,这样一组完成特定功能的指令有序集合称为程序。
(一)程序设计的步骤
从具体问题到编好程序要经过如下基本步骤:
(1) 分析课题——弄清问题的性质、目的,已知数据,运算精度以及速度等方面的要 求。
(2)确定算法——把实际问题转化为计算机求解的步骤和方法,即算法,而程序是用来描述算法的。
(3)画流程图——流程图是算法的一种直观而形象的表示方法,是对程序执行过程的 一种形象化的描述,又称为框图。
(4)编写程序——熟悉8086/8088的指令系统及程序设计常用技巧按流程图编写程 序。要求做到简单明了、层次清晰、运算迅速、少占内存。要编写高质量的汇编语言程序,必须加深对指令系统功能的理解,注意内存工作单元和工作寄存器的分配。
(5)上机调试、修改——可以通过单扳机或系统机进行调试、修改直至通过。
(二)程序的基本结构
程序的基本结构有四种:顺序结构,分支程序结构,循环程序结构,子程序结构。现 分节筒述于下。
二、顺序结构程序
顺序结构的程序又称简单程序,这种结构的程序是顺序执行的, 无分支,无转移,无循环,程序本身的逻辑很简单,它只依赖于计算机能够顺序执行指令(语句)的特点,只要语句安排的顺序正确即可。
例1 内存中自TABLESQ开始的16个单元连续存放着自然数0到15的平方值,任
给一数X(0≤X≤15)在XX单元中,查表求出X的平方值,将结果存入YY单元中。
首先在数据段中建立平方表,表的首地址为TABLESO,然后用程序找到X2值在平方表中的位置,即计算表地址,表地址 = 表起始地址(TABLESQ)+X,据此可写出如下程序。
DATA SEGMENT
TABLESQ DB O,1,4,9,16,25,36,49,
DB 64,81,100,121,144,169,225
XX DB ?
YY DB ?
DATA ENDS
STACK SEGMENT PARA STACK‘STACK’
DB 50 DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START PROC FAR * *
BEGIN: PUSH DS * *
MOV,AX,0 * *
PUSH AX * *
MOV AX,DATA
MOV DS,AX
MOV BX,OFFSET TABLESQ
MOV AH,0
MOV AL,XX
ADD BX,AX
MOV AL,[BX]
MOV YY,AL
RET * *
START ENDP * *
CODE ENDS
END BEGIN
其中带 * * 的六行语句的作用是为了在用户程序运行结束后,正确返回操作系统。 汇编语言源程序经过汇编、连接后生成 .EXE文件,可在DOS下直接键入文件名运行,例如命令 A>EXAMPLE 将把带有.EXE的文件EXAMPLE装入内存,并从程序中指定地址开始运行。装入文件并设置启动地址是由操作系统的COMMAND.COM文件完成的。COMMAND.COM文件在装入.EXE文件前,首先确定最低可用地址作为被装入程序的可用内存起点(该区段称为程序段)并在程序内从段内偏移地址处开辟100H字节的程序段前缀(PSP),在该PSP的前两字节存放一条 INT 20H 指令(即退出当前程序返回操作系统)。而COMMAND.COM把.EXE文件装入后,自动设置DS和ES寄存器指向程序段前缀,即此时的DS和ES值为程序段前缀之段基值。用户程序结束为RET,为使RET后程序转向程序段前缀中INT 20H指令,必须使CS为程序段前缀的段基值,IP为INT 20H的段内偏移值00H,因此必须把用户程序设计为过程,即加下START PROC FAR,在用户程序的前三条指令写入PUSH DS,MOV AX,0和PUSH AX,把DS = 程序段前缀之段基值,AX = 00H进栈,在用户程序最后用RET指令出栈到CS和IP,以保证程序转向INT 20H,进而返回操作系统。
代码段结束的处理还可用DOS系统功能调用,在结尾处安排如下两条指令
MOV AH、4CH
INT 21H
执行至此,返回调用程序,常用于应用DEBUG进行程序调试,可返回DEBUG。这 时,可不用把用户程序设计为过程,即去除带 * * 的六行语句。
例2 把非压缩十进制数转换为压缩十进制数,被转换数存放在DATl开始的两个单 元,转换后的数存回DATl单元。
程序如下:
MOV AX,DATI ;(AX)=0109H
MOV CL,4 ;(CL)=4
SAL AH,CL ;(AH)=10H
ROL AX,CL ;(AX)=0091H
ROL AL,CL ;(AL)=19H
MOV BYTE PTR DATl,AL ;(DATl)=19H
┅
DATl DW 0109H
三、分支结构程序
在实际的程序设计中,始终是顺序执行的情况是很少的,大部分程序在执行过程中,总是要求计算机作出一些判断,井根据判断作出不同的处理,这就引出了分支程序的概念。
(一)分支程序的二要素
分支结构程序是具有判断和转移功能的程序。
1.判断——根据运算结果的状态标志
判断前一定要经过运算(能影响状态标志的运算),状态标志反映了运算结果的特性。 这些状态标志是:进位标志CF、奇偶标志PF、零标志ZF、符号标志SF以及溢出标志OF。
2.转移 — 主要由条件转移指令来实现(也可用无条件转移指令JMP)
在8086/8088的指令系统中,条件转移指令可分为两大类,一类是按单标志位来判断 的,如JAE,JC,JZ,JO,JS等;另一类是按多标志位来判断,如JGE,JG,JA等,详见表3-6所示。
分支结构程序框图如图4—1所示。
(二)利用比较转移指令实现分支
这是实现分支的一种常用方法,用于比较、判断的指令是CMP(比较指令)、CMPS(串比较指令)以及SCAS(串搜索指令)等。转移指令已如前述。而分支的次数可由具体问题决定是单重分支还是多重分支。n次判断可形成n+l路分支。
例3 符号函数的处理
有一符号函数
1, 当 X>O (-128≤X≤127)
Y = O, 当 X=0
-1, 当 X<O
设给定值x存放于LY单元,函数Y值存放于YY单元,则按X的不同取值给Y赋值的程序如下:
MOV AL.XX
CMP AL,0
JGE BIGR
MOV AL,0FFH
MOV YY,AL ;X<0时,—1送入YY单元
HLT
BIGR:JE EQUL
MOV AL,1
一 MOV YY,AL ;X>O时,l送人YY单元
HLT
EQUL:MOV YY,AL ;X=O时,O送人YY单元
HLT
这是一个多重分支的符号函数赋值程序,其流程图见图4-2 所示。
例4数据块传送程序
要求把内存中某一区域的源数据块传送到另一区域。在编写该程序时,必须判别一下 源数据区同目的数据区之间有无重叠,以决定是增量传送还是减量传送。程序清单如下:
DATA SEGMENT
STRG DB l000 DUP(?)
STGl EQU STRG+7
STG2 EQU STRG+25
SIRSE EQU 50
DATA ENDS
STACK SEGMENT PARA STACK‘STACK’
STARN DB l000 DUP(?)
STACK ENDS
COSEG SEGMENT
ASSUME CS:COSEG,DS:DATA,ES:DATA,SS:STACK
GOO PROC FAR
BEGIN: PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV CX,STRSE
MOV SI,OFFSET STGl
MOV DI,OFFSET STG2
CLD ;增量方式
PUSHI SI
ADD SI,STRSE-1
CMP SI,DI
POP SI
JB OK
STD ;减量方式传送
ADD DI,STRSE-1 ;指向数据块底部
ADD SI,STRSE-1
OK:REP MOVSB ;重复传送50个数据
RET
GOO ENDP
COSEG ENDS
END BEGIN
(三)利用跳转表实现分支
1.跳转表的建立
在内存的一个连续区中,连续存放一系列跳转地址、跳转指令或关键字,组成一个决
定程序分支的跳转表。
例5 某工厂有8种产品的加工程序RO到R7分别存放在以SBRO,SBRl,…,SBR7为首地址的内存区域中,这8个首地址的偏移量连续存放在以BASE为首地址跳转表内,如图4-3 所示。
2.跳转表的使用
上例中,8种产品的编号为0,1,2,…,7 如果已知目前要加工的产品编号,要求编写程序,利用跳转表自动转入该产品的加工程序。
在跳转表已知的情况下,关键问题要计算所要求的加工程序的入口地址在跳转表中的地址,即计算表地址。从图 4-3可见:
表地址=表基地址+偏移量
表基地址即跳转表的首地址,偏移量即对应的程序入口地址在表中的地址与表基地址
的距离。在本例中,分析表的结构可见,偏移量=产品编号 * 2,已知偏移量后,即可求出表地址。据此可写出[例5]的程序清单
DATA SEGMENT
BASE DW SBR0,SBRl,SBR2,SBR3
DW SBR4,SBR5,SBR6,SBR7
BN DB ?
DATA ENDS
STACK SEGMENT PARA STACK ‘STACK’
DB l00 DUP(?)
STACK ENDS
COSEG SEGMENT
ASSUME CS:COSEG,DS:DATA,SS:STACK
START PROC FAR
BEGIN:PUSH DS
MOV AX,O
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV AL,BiN
MOV AH.O
ADD AL,AL
MOV BX,OFFSETBASE
ADD BX,AX
MOV AX,[Bx]
JMP AX
RET
START ENDP
COSEG ENDS
END BEGIN
3.根据跳转表内指令分支
在计算机系统的监控程序,键盘管理程序中经常要用到跳转表。在跳转表中既可存放 跳转地址(如例5),也可存放一系列的跳转指令JMP。
例6 有一监控程序的键盘程序控制12个命令键——执行键(EXEC)、存储器检查键(MEM)等等,按下任一命令键相当于发出一条键盘命令,而这些命令的实现分别由监控程序中的12个于程序完成,这些子程序的入口地址分别为ADR0,ADRl,…,ADRll。据此可以 组成命令键跳转表如图4-4所示。该命令键跳转表存放在以BASE0为首地址的内存区域中,表内存放着12条转移指令:
JMP ADR0,JMP ADRl, …,JMP ADRll。
设12个命令键的编号分别为0 ~ 11。命令跳转表中每三个单元存放一条转移指令,如果命令键的编号X已送入寄存器AL,则实现转向相应的命令子程序的程序如下
MOV AH,0
MOV BL,AL
ADD AL,AL
ADD AL,BL
ADD AL, BL
MOV BX,OFFSET BASE0
ADD BX,AX ;表地址计算
JMP BX
四、循环结构程序
(一)概述
1.循环程序
实现重复执行某一段程序的程序结构称为循环程序。用来处理带重复性的问题,可以缩短源程序及目标程序。
循环结构程序的框图之一如图4-5所示。
循环程序由五部分组成:
(1)初始化部分——这是循环的准备部分,为程序操作、地址指针、循环计数、结束条件等设置初始值。
(2)循环工作部分——这是循环程序的主体,完成程序的基本操作,循环多少次,这部分的语句就执行多少次。
(3)循环修改部分——修改循环工作部分的变量地址等,为下一轮重复操作作准备。
(4)循环控制部分——修改计数器或判断循环结束条件以决定是继续循环还是终止循环,是典型的分支结构,不过继续
循环的分支去向是循环体的头部。通常把循环工作部分称为循环体,循环体的第一条指令为循环体的头部。
(5)循环结束部分——循环终止后,对循环结果的处理部分。
2.循环程序的几种结构形式
循环程序除图4-5所示的基本结构形式外,还有两种常用的结构形式,如图4-6和图
4-7所示。
这两种结构的特点是工作部分放在循环控制部分之后,这样可以允许循环次数为零的 循环程序或不需要循环时不进入循环工作部分。
3.循环程序分类
(1)按循环控制条件分类
1)计数循环——重复次数已知,用计数值控制循环的开始与终止。
2)条件控制循环——重复次数未知或不确定,需找出循环控制的条件。
(2)按循环体内结构分类
1)单重循环——循环体内只是顺序程序或分支程序,不再有循环程序。
2)多重循环——循环体内再套有循环程序。可把重复处理部分独立出来,前面加上 循环准备,后面加上结束判断。
4.循环程序设计要点
循环程序设计要点可归结为两点:
(1)怎样把求解的问题变为循环结构的程序类型——怎样实现重复,即计算方案的循 环化。这里循环工作部分的设计尤为重要,因为这是多次重复的部分,注意程序的精练,及循环体头部的确定。
(2)怎样使循环准确地执行完毕,这就要注意循环控制部分的设计。特别是对条件控 制的循环,注意设置循环结束标志。
(二)循环程序举例
例7计算Y=∑ai , 0≤i≤100
有100个数a1,a2,…,a100,求这100个数之和(设和值不大于2个字节)。采用循环结构可写出如下程序:
DATA SEGMENT
TABL DW a1,a2,…,a10
DW all,a12,…,a20
…
DW a91,a92,…,a100
YY DW ?
DATA ENDS
STACK SEGMENT PARA STACK‘STACK’
DB l00 DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
GO PROC FAR
BEGIN:PUSH DS
MOV AX,O
PUSH AX
MOV AX,DATA
MOV DS, AX
MOV AX,0
MOV BX, OFFSET TABL
MOV CX, 100
LOP: ADD AX,[Bx]
INC BX
INC BX
DEC CX
JNZ LOP
MOV YY, AX
RET
GO: ENDP
CODE ENDS
例8 统计数组中负元素的个数
数据段的定义如下:
DATA SEGMENT
D1 DB -1,-3,5,6,-9,…
COUNT EQU $-D1
RS DW ?
DATA ENDS
代码段程序为:
CODE SEGMENT
ASSUME CS:CODE,SS:STACK, DS:DATA
START PROC FAR
BEGING: PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV BX,OFFSET D1 ;建立数据指针
MOV CX,COUNT ;置计数器初值
MOV DX,0 ;置结果初值
LOP1: MOV AL,[BX]
CMP AL,0
JGE JUS
INC DX
JUS: INC BX
DEC CX
JNZ LOP1
MOV RS,DX
RET
START ENDP
CODE ENDS
以上两例都是‘‘先执行,后判断”的结构。
例9 寄存器AX中有一个16位二进制数,编程统计其中“1”的个数,结果存放在
CX中。
本例最好采用“先判断、后执行”的结构,即先检查凡x中有无为“1”的位。则代码段的有关程序如下:
MOV CX, 0
LOP: AND AX, AX
JZ STP
SAL AX, 1
JNC LOP
INC CX
JMP LOP
STP: HLT
例10 软件延时程序如下:
SOFTDLY PROC
MOV BL,10
DELAY: MOV CX, 2801
WAIT: LOOP WAIT
DEC BL
JNZ DELAY
RET
SOFTDLY ENDP
这是一个二重循环结构,内循环即WAIT:LOOPWAIT,每个内循环可实现延时10ms左右,外循环人口为DELAY:MOV CX,2801,共进行10次,总的延时时间约lOOms。
注意:可以从内循环直接跳到外循环,但不能从外循环直接跳进内循环。特别要注意 100循环体头部,防止死循环。
五、子程序
(一)概述
1.子程序结构
如果在一个程序中的多个地方或在多个程序中都要用到同一段程序,可以把该程序段 独立出来存放在内存的某一区域以供其他程序调用,这段程序称为子程序或过程。可见子 程序是可供其他程序调用的独立的、相对固定的程序段。调用子程序的程序体称为主程序 或调用程序。在实用中总把常用的子程序标准化后存放在一个内存区中,称为“子程序 库”。
(1)结构。子程序的第一个语句前必须有“过程名”——入口地址的符号表示,出口是返回指令RET。
(2)调用与返回。主程序通过书写调用指令CALL后跟子程序的入口地址来调用子 程序,8086/8088允许子程序在现行代码段中(子程序名为NEAR类型),也可以不在现行代码段中(子程序名为FAR类型)。为了保证正确返回,RET指令的类型,必须与CALL指令的类型相匹配。
1)调用指令中的目标地址有两种表示方法:
a.直接调用,目标地址就在指令中。例如:CALL NEAR-PRG 。
b.间接调用,目标地址在由指令指定的寄存器或内存单元中。例如:CALL DWORD PTR[BX] 对段内的直接调用而言,CALL指令首先将SP减2,使断点的IP栈,从指令中得到的目标过程的相对偏移量(最大不能越界)加到IP上。对段内的间接调用而言,以CALL AX为例,SP减2,IP进栈,AX→IP。
对段间的直接调用而言,SP减2,现行代码段寄存器CS的内容进栈,CS由指令中目标过程的段基值代入。SP再减2,IP进栈,然后将指令中的偏移地址代入IP。
对段间的间接调用而言,SP减2,把现行的CS值进栈,CS由指令指定的双字存储器指针的第二个字的内容代入,SP再减2,IP进栈,然后IP由指令中指定的双字指针的第一个字的内容代入。双字存储器由数据段定义。
2)返回指令RET也有两种情况:
a. 段内返回指令,把SP所指的堆栈顶部的一个字的内容弹回IP,SP加2。
b.段间返回指令,把SP所指的堆栈顶部的两个字的内容,先弹回IP,后弹回CS,SP加4。
2.子程序文件
子程序应以文件形式编写,子程序文件由子程序说明和子程序构成。
(1)子程序说明。子程序说明包括如下部分:
1)功能描述:包括子程序名称、功能,以及性能指标(如执行时间)等o
2)所用寄存器和存储单元。
3)子程序的入口、出口参数。
4)子程序中又调用的其他子程序。
5)调用实例(可无)。
子程序说明举例如下:
;子程序PTOB
;两位十进制数(BCD码)转换成二进制数
;入口参数:AL中存放被转换数
;出口参数:CL中存放转换后的二进制数
;所用寄存器:BX
;执行时间:0.06ms
(2)子程序。在8086/8088中,子程序本身常以过程形式存放在代码段中,以一个过 程名开始,以RET指令结束。
例如: DTOB PROC
…
RET
DTOB ENDP
3.子程序应用中应注意的问题
(1)主程序与子程序的连接。主程序与子程序的连接由下例可见:
例如;
CODE SEGMENT
START:…
MOV AL,XX ;被转换的数在XX单元
PUSH BX
CALL DTOB
MOV YY,CL ;转换结果存于YY单元
POP BX
…
DTOB PROC
RET
DTOB ENDP
CODE ENDS
END START
(2)子程序中所用寄存器及工作单元内容的保护。为了不破坏原有信息,对于在子程 序中要用到的某些寄存器和存储单元的内容,必须压入堆栈加以保护,也可存入一些空闲单元或某些目前不用的寄存器中。称为现场信息的保护。保护可以在子程序中实现,可在主程序中实现,一般在子程序中实现保护比较好。而对于用作中断服务的子程序,则一定在子程序中安排保护指令,因为中断的出现是随机的,无法在主程序中安排保护程序。
(3)参数的传递。主程序与子程序通常需要交换信息,在多数情况下主程序需要向子 程序传递参数,子程序执行的结果要传送给主程序。子程序清单中的人口参数使子程序 对不同的数进行处理,出口参数可送出不同的结果。
参数传递一般有三种方法:
1)用寄存器传递,适用于参数较少的情况;
2)用参数表传递,适用于参数较多的情况,要求事先建立参数表,参数表一般建立在内存中。
3)用堆栈传递,适用于参数多并子程序有嵌套、递归调用的情况,主程序将参数压入堆栈,子程序中将参数从堆栈弹出。
4.子程序嵌套和子程序递归
(1) 子程序嵌套 。子程序调用别的子程序,称为嵌套,。嵌套的层次只受空间的大小
的限制。
(2)子程序递归。子程序直接或间接地调用子程序自身,称为递归。
(二)子程序结构举例
例11 数据段定义了两个数组ARYl和,ARY2,编一程序分别求此两数之和。主程序和过程分别安排在两个不同的段中,因此过程应是FAR类型。
该程序清单如下
STACK SEGMENT PARA STACK
DW 20 DUP(?)
STACK ENDS
DATA SEGMENT
ARY1 DB 100 DUP(?)
SUM1 DW ?
ARY2 DB 50 DUP(?)
SUM2 DW ?
DATA ENDS
MAIN SEGMENT ;主程序段
ASSUME CS:MAIN, DS:DATA,SS:STACK
STR:PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV AX,SIZE ARY1
PUSH AX ;SUM过程的入口参数1 进栈
MOV AX,OFFSET ARY1
PUSH AX
CALL SUM
…
MOV AX,SIZE ARY2
PUSH AX ;SUM过程的入口参数2 进栈
MOV AX,OFFSET ARY2
PUSH AX
CALL SUM
HIT
MAIN ENDS
PROCE SEGMENT ; 过程段
ASSUME CS:PROCE,DS:DATA,SS:STACK
SUM PROC FAR
PUSH AX
PUSH BX
PUSH CX
PUSH BP
N40V BP,SP
PUSHF
MOV CX,[BP+14]
MOV BX,[BP+12]
MOV AX,0
AND: ADD AL,[BX]
INC BX
ADC AH,0
LOOP ADN
MOV[BX],AX ;数组之和送到结果区
POPF ;恢复现场
POP BP
POP CX
POP BX
POP AX
RET 4 ;返回并废除参数1和2
SUM ENDP
PROCE ENDS
END STR
从程序清单可见主程序向过程的参数传递是通过堆栈实现的,程序执行过程中堆栈变化如图所示。
图4-8 例u中堆栈变化情况
程序中出现RET 4,该指令的功能是返回后,SP再加 4
例12 求n!
算法是
n! = n(n-1)! 当n>0时;
1 当n=0时
设数n存放在AL中,n! 存放在BX中。
;主程序
MAIN: MOV AX,3 ;设n=3
CALL FACT
X1: MOV BX,DX
HLT
;阶乘子程序
;人口参数:AL中存放n
;出口参数:DX中存放n!
;所用寄存器:CX
FACT PROC
CMP AL,0
JNZ IIA
MOV DL,1
RET ;①
IIA: PUSH AX
DEC AL
CALL FACT
X2: POP CX
CALL MULT
X3: MOV DX,AX
RET ;②
FACT ENDP
;无符号字节数乘法子程序
;入口参数:CL、DL中各为一乘数
;出口参数:AX中为乘积
MULT PROC
…
RET ;③
MULT ENDP
该程序既包括子程序嵌套,也包括子程序递归。该子程序名为FACT,嵌套子程序即
FACT调用另一子程序MULT(无符号字节数乘法子程序),递归子程序即FACT调用FACT。注意三个RET指令的作用。
热点关注:
广东自考成绩查询【小程序查分】图文流程方式
广东省2020年1月自考成绩于3月31日公布