第 6章 子 程 序 设 计第 6章 子 程 序 设 计
6.1 子程序的概念与特性
6.2 子程序的结构形式
6.3 子程序调用和返回指令
6.4 子程序的设计
6.5 子程序的参数传递方法
6.6 子程序的嵌套与递归
6.7 综合举例第 6章 子 程 序 设 计
6.1 子程序的概念与特性
6.1.1 子程序的概念人们在编写程序时,常常会遇到这样的情况:某些完成相同的功能,只是加工的数据略有不同的指令组 (程序段 )需要在一个程序的若干不同地方使用多次,或是在一个程序中的多个地方或多个程序中的多个地方用到了同一段程序。这些程序段的功能和结构形式都相同,只是某些变量的赋值不同。那么就可以将这段程序抽取出来存放在某一存储区域,每当需要执行这段程序时,就转到这段程序去执行,执行完后再返回到原来的程序继续运行。把抽取出来的这段具有特定功能的程序段称为子程序。
第 6章 子 程 序 设 计例如,计算某个数的立方根可能在一个程序中使用多次,但每次自变量不同。又如,设计名字识别程序时,有时需要判断字符是否为字母,有时又需要判断字符是否为分隔符。这样的指令组所包含的指令,少则几条十几条,多则数十条几百条。如果每一处都重复把它写一次,这显然太浪费程序设计的时间和计算机的存储空间。但这又不能设计成循环程序。因此,人们通过实践,
在程序设计的早期就想出了一种较好的办法,就是把这组指令分离出来,单独写成一个所谓的,子程序,,并建立进入它和从它出来时所需要的连接信息。只需在需要处调用这个子程序就行。
换言之,子程序方法使得人们把,多次编写,的情况转变成,一次编写,多次调用,的情况。子程序相当于高级语言中的过程或函数。
第 6章 子 程 序 设 计图 6-1 主程序与子程序的关系通常,把要调用程序的那个程序称为主程序或调用程序,也称转子;而把被调用的程序称为子程序或被调用程序,也称返主。
把它们之间控制的转移称为子程序的连接。主程序与子程序的关系如图 6-1所示。
转子 子程序主程序返主第 6章 子 程 序 设 计在程序设计的实际应用中,子程序的引入可以节省存储空间及程序设计所花费的时间,有利于设计一个大而复杂的程序,
即把一个大而复杂的程序设计成一个主程序和若干个子程序。
这有助于减少程序的复杂性,便于模块化设计,也便于程序的调试及修改等。但子程序也有其不足之处,这就是要多花费一些机器时间。
第 6章 子 程 序 设 计
6.1.2 子程序的分类子程序的种类很多,有单入口、单出口子程序,也有多入口、多出口子程序。对于一些复杂的子程序,还常常要调用别的子程序。这种调用可以是一个子程序调用另外的一个子程序,
也可以是一个子程序调用自身,这就形成了嵌套结构和递归结构,分别称为嵌套子程序和递归子程序。子程序嵌套的示意图如图 6-2所示。
第 6章 子 程 序 设 计图 6-2 嵌套子程序示意图子程序
1
主程序子程序
2
第 6章 子 程 序 设 计
6.1.3 子程序的特性
1.通用性子程序具有通用性,便于共享。例如,键盘管理程序,磁盘读写程序,标准函数程序等等,许多程序中要用到这些程序,
这种可共享的程序最适宜写成子程序。而只能完成特定功能的子程序,由于缺乏通用性,难于共享,就不适宜写成子程序。
第 6章 子 程 序 设 计
2.重复性子程序是可多次重复使用的。 — 个子程序只占一段存储空间,但可以多次地调用它,这样就避免了编程人员的重复劳动,
节省了存储空间。由于增加了调用、返回指令以及现场保护,
因此程序执行时间会增长。如果一个程序段只用到一次,就没有必要编写成子程序形式。
第 6章 子 程 序 设 计
3.可重定位性可重定位性是指子程序可以存放在存储区的任何地址处。
如果子程序只能存放在固定的地址处,则在编写主程序时要特别注意存储单元的分配,不要使主程序占用了子程序的存储单元而破坏子程序,这样就会给编程人员带来很大麻烦,而且在装配主程序和子程序时往往造成存储空间的冲突或浪费。为了使子程序可重定位在内存的任意区域中,编制子程序时,不应采用绝对地址,而应全部使用相对地址。
第 6章 子 程 序 设 计
4.可递归性前面已提及递归程序的概念。在图 6-2中,当子程序 1和子程序 2是同一个程序时,这种调用就是递归调用。为使子程序具有可递归性,应当利用堆栈和寄存器作为中间结果的暂存器,
而不能用固定的存储单元做暂存器。
第 6章 子 程 序 设 计
5.可重入性可重入性是指子程序可被中断并能再次被中断程序调用。
具体地讲就是,如果子程序可被中断,在中断处理中又被中断服务程序调用,并且能为中断服务程序和中断了的子程序提供正确的结果,这种子程序就是可重入的。同样,为使子程序具有可重入性,也应当利用堆栈和寄存器作为中间结果的暂存器,
而不能用固定的存储单元做暂存器。
第 6章 子 程 序 设 计
6.2 子程序的结构形式
6.2.1 子程序的定义子程序必须经过定义以后才能被调用。其定义格式如下:
过程名 PROC 类型属性;过程体过程名 ENDP
功能:实现子程序的定义。
…
第 6章 子 程 序 设 计说明:
(1) PROC和 ENDP是过程的定义符和结束符,它们必须成对出现,它们前面的过程名必须一致,过程名为一标识符,它的写法和标号的写法相同,它实际上是子程序入口的符号地址。
(2) 类型属性可以是 NEAR或 FAR两种类型,缺省时为 NEAR属性。过程属性的确定方法是当调用过程 (主程序 )和被调用过程
(子程序 )在同一个代码段中则用 NEAR属性;当调用过程和被调用过程不在同一个代码中则使用 FAR属性。
第 6章 子 程 序 设 计
(3) 在子程序设计中,除了子程序必须经过定义以外,还必须解决主程序和子程序的链接、主程序和子程序的参数传递这两个问题。主程序和子程序的链接是通过执行调用指令和返回指令实现的,而主程序和子程序的参数传递可有多种方式,
这将在 6.5节中讨论。先讨论第一个问题。
1) 调用程序和子程序在同一个代码段的程序结构若调用程序和子程序在同一个代码段内,其程序框架如例 6-1
所示。此程序给出了代码段中含有主程序和一个子程序的情况,
实际上可以含有多个子程序,子程序可以是 NEAR型或缺省。注意段长不能超过 64 KB。
第 6章 子 程 序 设 计例 6-1
CODE SEGMENT
MAIN PROC FAR
PUSH DS
MOV AX,0
PUSH AX;其他代码
CALL SUBA
RET
…
…
第 6章 子 程 序 设 计
MAIN ENDP
SUBA PROC
RET
SUBA ENDP
CODE ENDS
END MAIN
…
例 6-1的子程序 SUBA与主程序 MAIN同处在一个代码段 CODE之中。
第 6章 子 程 序 设 计
2) 调用程序和子程序在不同代码段的程序结构若调用程序和子程序在不同的代码段中,其程序框架如例
6-2所示。其中的子程序为 FAR属性,CALL指令显式说明其 FAR
属性。此程序只给出了一个模块内的多个代码段的情况,也可将程序设计为多模块的情况。
第 6章 子 程 序 设 计例 6-2
CODEA SEGMENT
MAIN PROC FAR
PUSH DS
MOV AX,0
PUSH AX;其他代码
CALL FAR PTR SUBB
RET
MAIN ENDP
CODEA ENDS
…
…
第 6章 子 程 序 设 计
CODEB SEGMENT
SUBA PROC FAR
CALL FAR PTR SUBB
RET
SUBA ENDP
SUBB PROC FAR
RET
SUBB ENDP
CODEB ENDS
END MAIN
…
…
…
第 6章 子 程 序 设 计由于 SUBB既被段间调用又被段内调用,所以必须是 FAR属性。
例 6-2给出的子程序 SUBA,SUBB与主程序 MAIN不在同一代码段中,主程序 MAIN在 CODEA代码段中,而子程序 SUBA,SUBB在
CODEB代码段中。
例 6-1和例 6-2说明,汇编语言程序中的主、子程序,既可以在同一个代码段中 (如例 6-1),也可以在不同的代码段中 (如例 6-2)。
第 6章 子 程 序 设 计
6.2.2 子程序调用方法说明一个完整的子程序,应当包括子程序调用方法说明、保护现场和恢复现场、子程序定义等部分。为了使用的方便,子程序应以文件形式编写。子程序文件由子程序说明和子程序本身构成。
子程序说明部分要求语言简明、确切。
子程序说明一般由如下几部分组成:
(1) 功能描述:包括子程序的名称、功能、性能指标 (如执行时间 )等。
(2) 所用的寄存器和存储单元。
(3) 子程序的入口、出口参数。
(4) 子程序中又调用的其他子程序。
(5) 调用实例。
第 6章 子 程 序 设 计例如,有一子程序说明如下:; 子程序 DTOB; 将两位十进制数 (BCD码 )转换成二进制数; 入口参数,AL寄存器中存放十进制数; 出口参数,CL寄存器中存放转换完的二进制数; 所用寄存器,BX; 执行时间,0.06 ms
第 6章 子 程 序 设 计看了这一子程序说明,尽管还不知道子程序本身的情况,
但根据说明已可以调用这个子程序了。该子程序说明的第一、
二行告诉我们 DTOB子程序完成的功能。入口参数说明这一程序在调用前应将要转换的十进制数送入 AL寄存器。出口参数说明,
子程序执行完后,转换结果就在 CL寄存器中,所用寄存器是说该子程序执行过程中要用到 BX寄存器,因此在调用本子程序前,
若 BX寄存器里存放着有用数据的话,应该事先转存或保护起来,
否则可能会被破坏。执行时间则说明了该程序执行所需的时间是 0.06 ms。有的说明中还给出一个调用实例,以实例的形式教给用户如何使用和调用子程序。
第 6章 子 程 序 设 计
6.3 子程序调用和返回指令
6.3.1 调用指令
1.段内直接调用格式,CALL DST
CALL NEAR PTR DST
功能:将子程序的返回地址 (断点地址 )存入栈中,并转移到子程序入口地址去执行子程序。
执行的操作,(SP)-2→SP
(IP)→(SP)+1,(SP)
(IP)+ D16→IP
第 6章 子 程 序 设 计该指令先把返回地址 (即主程序中 CALL指令的下一条指令的地址 )保存到堆栈中,以便子程序执行完返回主程序时使用,
然后则转移到子程序的入口地址去执行子程序。指令中的 DST
一般是过程名 (被调用过程 ),即子程序入口的符号地址,D16是子程序入口地址和 CALL指令之间的偏移量。
第 6章 子 程 序 设 计例 6-3
CODE SEGMENT
MAIN PROC FAR
CALL SUBR1
MAIN ENDP
SUBR1 PROC NEAR
SUBR1 ENDP
CODE ENDS
…
…
…
第 6章 子 程 序 设 计图 6-3 段内直接调用指令 CALL执行示意图
FF
00
01
…
…
…
0 6 0 3 H
0 5 0 3 H
0 5 0 2 H
0 5 0 1 H
0 5 0 0 H
代码段
C A L L S U B R 1
(I P )
S U B R 1
03
05
堆栈
(I P )
(b ) (c )
堆栈
(S P )
(a )
第 6章 子 程 序 设 计本例中执行 CALL SUBR1时,自动将返回地址 (IP)= 0503H,
入栈保存,然后执行 (IP)+D16= 0503H+0100H= 0603H→IP,转到子程序 SUBR1去执行。
图 6-3中,(a)图是 CALL SUBR1执行前堆栈; (b)图是 CALL
SUBR1执行后堆栈; (c)图是 CALL SUBR1机器指令格式示意图。
第 6章 子 程 序 设 计
2.段内间接调用格式,CALL DST
CALL WORD FAR DST
其中,DST为通用寄存器或字存储器。
功能:将子程序的返回地址 (断点地址 )存入栈中,并转移到子程序的入口地址执行子程序。
执行的操作,(SP)-2→SP
(IP) →(SP)+1,(SP)
(EA) →IP
其中,EA是由 DST的寻址方式所确定的有效地址。
第 6章 子 程 序 设 计例 6-4
CALL DX ;子程序的入口地址存于寄存器 DX中
CALL WORD PTR[SI] ;子程序的入口地址在存储单元中第 6章 子 程 序 设 计
3.段间直接调用格式,CALL FAR PTR DST
其中,DST为子程序名。
功能:将子程序的返回地址 (断点地址 )存入栈中,并转移到子程序的入口地址执行子程序,子程序入口地址的偏移地址送 IP,子程序入口地址的段地址送 CS。
第 6章 子 程 序 设 计执行的操作:
(SP)-2→SP
(CS) →(SP)+1,(SP)
(SP)-2→SP
(IP) →(SP)+1,(SP)
偏移地址 → IP
段地址 → CS
第 6章 子 程 序 设 计段间直接调用指令同样是先保存返回地址,然后转移到由
DST指定的地址去执行。和段内调用指令所不同的是,此时的主程序和子程序不在同一个代码段,因此返回地址的保存及转向地址的设置都必须把段地址考虑在内。指令中的 DST可以是子程序入口的符号地址,而在机器语言格式中指定的子程序入口地址的偏移地址就在指令的第 2,3个字节中,子程序入口地址的段地址就在指令的第 4,5个字节中,即子程序入口的偏移地址和段地址在操作码的后面,是指令的一部分。
第 6章 子 程 序 设 计例 6-5 显示一个字符 M,并输出回车和换行,要求采用段间直接调用的方式。
STACK SEGMENT PARA STACK 'STACK'
DB 100 DUP(?)
STACK ENDS
CODEA SEGMENT
ASSUME CS∶CODEA,SS∶STACK
START,CALL FAR PTR DISP
MOV AH,4CH
INT 21H
CODEA ENDS
第 6章 子 程 序 设 计
CODEB SEGMENT
ASSUME CS∶CODEB
DISP PROC FAR
MOV DL,'M'
MOV AH,2
INT 21H
MOV DL,0DH ;输出回车符
MOV AH,2
INT 21H
MOV DL,0AH ;输出换行符
MOV AH,2
INT 21H
RET
DISP ENDP
CODEB ENDS
END START
第 6章 子 程 序 设 计
4.段间间接调用格式,CALL DWORD PTR DST
其中,DST是双字存储器操作数。
功能:将子程序的返回地址 (断点地址 )存入栈中,并转移到子程序的入口地址执行子程序,由 DST的寻址方式确定的有效地址 EA送 IP,EA+2送 CS。
第 6章 子 程 序 设 计执行的操作,(SP)- 2→SP
(CS)→(SP)+1,(SP)
(SP)- 2→SP
(IP)→(SP)+1,(SP)
(EA)→IP
(EA+2)→CS
例 6-6 CALL DWORD PTR[BX]
CALL DWORD PTR ADDR
第 6章 子 程 序 设 计
6.3.2 返回指令返回指令 RET通常作为子程序的最后一条指令,它使子程序在执行完以后能返回主程序去继续执行。由于主程序在执行
CALL指令调用子程序时,将返回地址已保存在堆栈中,所以执行 RET指令时,只须将返回地址弹出栈顶并送 IP。若是段间返回,还须将段地址弹出栈顶并送 CS。
第 6章 子 程 序 设 计
1.段内返回指令格式,RET
功能:根据存放于栈中的断点地址返回主程序。
执行的操作,((SP)+1,(SP)) →IP
(SP)+2→SP
第 6章 子 程 序 设 计
2.段内带立即数返回指令格式,RET n
功能:根据存放于栈中的断点地址返回主程序。
执行的操作,((SP)+1,(SP)) →IP
(SP)+2→SP
(SP)+n→SP
第 6章 子 程 序 设 计其中,n为一表达式,根据它计算出的值获取相应的位移量。
它的作用是允许返回地址弹栈后修改栈顶指针。调用程序在执行 CALL指令调用子程序之前把子程序所需要的参数入栈,以便子程序运行时使用这些参数。当子程序执行完返回调用程序后,
这些参数已不再需要,利用 n修改栈顶指针使 SP指向参数入栈以前的位置。
第 6章 子 程 序 设 计
3.段间返回指令格式,RET
功能:根据存放于栈中的断点地址返回主程序。
执行的操作,((SP)+1,(SP)) →IP
(SP)+2→SP
((SP)+1,(SP)) →CS
(SP)+2→SP
第 6章 子 程 序 设 计
4.段间带立即数返回指令格式,RET n
功能:根据存放于栈中的断点地址返回主程序。
执行的操作,((SP)+1,(SP)) →IP
(SP)+2→SP
((SP)+1,(SP)) →CS
(SP)+2→SP
(SP)+ n→SP
这里 n的含义与段内带立即数返回指令中的含义相同。调用指令
CALL和返回指令 RET都不影响条件码。
第 6章 子 程 序 设 计需要说明的是,CALL和 RET指令都有类型属性的问题,即段内调用和返回为 NEAR属性,段间调用和返回为 FAR属性。 80x86
的汇编程序用过程定义 PROC的类型属性来确定 CALL和 RET指令的属性。也就是说,如果所定义的过程是 FAR属性,那么对它的调用和返回一定都是 FAR属性;如果所定义的过程是 NEAR属性,那么对它的调用和返回也一定是 NEAR属性。这样,用户只需要在定义过程时考虑它的属性,而 CALL和 RET指令的属性就可以由汇编程序来确定了。
第 6章 子 程 序 设 计
6.4 子程序的设计从主程序与子程序之间的调用关系来看,它们有如下一些特点:对子程序而言,要被多次使用,每次都应从主程序得到不同的初始数据;而且要把加工后的结果告诉主程序;要能正确地返回到调用的地方 (调用地点不同,因而返回处也随之不同 )。对主程序而言,在多次调用子程序时,需要把不同的调用点告诉子程序;要提供供子程序加工的数据 (因调用点不同,所提供的数据一般也各异 );还要把结果从子程序中取回,以供使用。
第 6章 子 程 序 设 计根据以上特点,它们各自的工作步骤一般是:
主程序:
(1) 在约定位置给出子程序处理所需的信息;
(2) 转向子程序的入口,即将控制转向子程序,同时,要给出返回地址信息;
(3) 当由子程序返回主程序时,主程序从约定的位置取出处理结果。
第 6章 子 程 序 设 计子程序:
(1) 保留返回点的信息,保存子程序要使用的通用寄存器,
以免其内容被破坏;
(2) 取出要加工的初始数据进行处理,并向约定位置送处理后的结果;
(3) 按返回点信息返回主程序,即将控制转到主程序。
在这里,需要对子程序设计中的几个问题:主程序与子程序的连接、所用寄存器及工作单元内容的保护、参数的传递、
现场保护和恢复等作一讨论。其中,对参数的传递方法的讨论在 6.5节中进行。
第 6章 子 程 序 设 计
1.主程序与子程序的连接例 6-7 如下程序段:
CODE SEGMENT
STRT:
MOV AL,XX ;设要求转换的数在 XX单元
PUSH BX
CALL DTOB
NEXT,MOV YY,CL ;转换结果存放在 YY单元
POP BX
DTOB PROC
RET
DTOB ENDP
CODE ENDS
END STRT
…
…
…
第 6章 子 程 序 设 计主程序向子程序的转移是由 CALL指令完成的。这个主程序中的 MOV AL,XX指令是传送入口参数的,PUSH BX指令保护 BX寄存器的内容。 CALL指令的执行分两步进行:
(1) 先将 CALL指令下面一条指令的地址 NEXT,即断点入栈,
也称保护断点。
(2) 程序转到 DTOB去执行,实现转子。
子程序执行到最后一条指令 RET时,将栈的内容 (断点 NEXT)
弹出到指令指针中,使程序从 NEXT处指令继续执行,实现了从子程序返回。 POP BX将恢复 BX的内容。
第 6章 子 程 序 设 计
2.寄存器及所用工作单元内容的保护如果在子程序中要用到某些寄存器或存储单元,为了不破坏原有信息,要将它们的内容入栈加以保护,存入另外一些空闲的存储单元或某些目前不用的寄存器中。
保护可以在子程序中实现,也可以在主程序中实现。一般情况下,在子程序的开始部分安排一段保护程序,用以对它所用到的寄存器或存储单元内容加以保护。在子程序结束前,再将有关的内容恢复。例如:
第 6章 子 程 序 设 计
DTOB PROC
PUSH BX
POP BX
RET
DTOB ENDP
…
第 6章 子 程 序 设 计在子程序中实现保护比较好,遇到粗心的用户忘记在调用前安排保护的话,有关内容也不会破坏。如果在子程序中没有安排保护指令,则可以在主程序中,在调用前安排保护指令,调用后安排恢复指令。用于为中断服务的子程序,则一定要在子程序中安排保护指令。因为中断是随机出现的,也就是说,主程序中断而转入子程序的地点是不固定的,无法在主程序安排保护程序。
第 6章 子 程 序 设 计
3.参数的传递子程序中允许改变的数据叫参数。参数分为入口参数和出口参数。参数的使用使子程序具有灵活、方便、通用的优点。
入口参数使子程序可对不同数据进行处理,出口参数使子程序可送出不同的结果。一般的子程序都可接收参数。
第 6章 子 程 序 设 计参数传递的方法一般有三种:
(1) 寄存器传递:适合于参数较少的情况。
(2) 参数表传递:适合于参数较多的情况,但要求事先建立一个参数表,参数表可建在内存中或外设端口中。
(3) 栈传送:适合于参数多且子程序有嵌套或递归调用的情况,主程序将参数入栈,子程序将参数从栈中弹出。
无论哪种方法,主程序与子程序的接口要设计好。子程序要求到哪里取参数,主程序就应将参数送到那儿,而且要注意参数的个数、顺序和类型。
第 6章 子 程 序 设 计
4.现场保护和现场恢复现场保护就是在子程序的功能实现前,把将要用到的寄存器中的原有内容保存起来。现场恢复就是子程序的功能实现后,
将数据取出再送回原来的寄存器中,以保证子程序执行前后,这些寄存器的内容不被改变,从而不影响主程序对这些寄存器的使用。
在 80x86中,现场保护是通过入栈指令 PUSH来实现的,而现场恢复是通过弹栈指令 POP来实现的。不论是现场保护还是现场恢复,都需要使用栈。设有寄存器 AX,BX,CX,DX和 EX需要保护,若现场保护 (即入栈的顺序 )是 AX,BX,CX,DX和 EX;由于栈的操作是采用先进后出的方式,因此,现场恢复 (即弹栈的顺序 )恰好与入栈的顺序相反,应是 EX,DX,CX,BX和 AX,程序设计为第 6章 子 程 序 设 计
SUBA PROC
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH EX;子程序中的其他代码
POP EX
POP DX
POP CX
POP BX
POP AX
RET
SUBA ENDP
…
第 6章 子 程 序 设 计
6.4.1 子程序的设计子程序的编写应采用较好的算法,这可使子程序运行速度比较快,又节省内存,真正达到子程序在一个程序中能被多个程序使用或在多处被用到的目的。根据 6.2.2节的叙述,编写子程序时还应提供有关子程序的足够信息,使得使用时不再需要考虑子程序的内部结构,就能方便地调用。
例 6-8 插入字符串。
编程在一个给定的字符串指定的位置上插入另一个字符串,
在这里,给定的字符串称为目的字符串,插入的另一个字符串称为源字符串,如图 6-4所示。
第 6章 子 程 序 设 计图 6-4 插入字符串示意图源字符串 要求插入的位置目的字符串目的字符串执行前执行后第 6章 子 程 序 设 计本题采用子程序的方法设计。
1) 子程序调用方法说明子程序名,STRINSERT。
功能:在一个给定的字符串指定的位置上插入另 — 个字符串。
输入:在进入时,DS∶BX 指向源字符串,ES∶BP 指向目的字符串,ES∶DX 指向要在目的字符串中插入源字符串的位置。
每个字符串都是以一个说明字符串长度的 16位整数开始的。
第 6章 子 程 序 设 计输出:返回时,已在目的字符串指定的位置中插入了源字符串,因此目的字符串长度增加了。
寄存器占用:不用修改寄存器。
段访问:在输入时,数据段必须包含源字符串,附加段必须包含目的字符串。
子程序调用:无。
注释:无。
第 6章 子 程 序 设 计
2) 子程序清单
ESS EQU ES:[SI]
DSD EQU BYTE PTR[DI]
STRINSERT PROC FAR
PUSH SI
PUSH DI
PUSH CX
PUSH AX
MOV SI,BP
ADD SI,ES:[SI]
INC SI
MOV DI,SI
第 6章 子 程 序 设 计
MOV AX,[BX]
ADD DI,AX
ADD ES:[BP],AX
MOV CX,SI
SUB CX,DX
INC CX
STD
REP MOVS DSD,ESS
MOV DI,DX
MOV SI,BX
CLD
LODSW
MOV CX,AX
第 6章 子 程 序 设 计
REP MOVSB
STRINSERTEXIT:
POP AX
POP CX
POP DI
POP SI
RET
STRINSERT ENDP
第 6章 子 程 序 设 计例 6-9 从键盘键入一个不超过 65 535的无符号整数,然后把它转换成等值的二进制数,最后再把该数以十六进制的形式在显示器上显示出来。用子程序编制程序。
程序的设计可分两步完成:
① 实现十进制数转换成二进制数,即将键入的 0~ 9数字的
ASCII码转换成对应的二进制数值,然后利用,累加和乘以 10加键入值,的算法将一个十进制数转化为等值的二进制数。
② 将转换好后的二进制数再以十六进制的形式在显示器上显示出来。
第 6章 子 程 序 设 计具体实现时,采用子程序的结构。首先用 DECBIN子程序实现从键盘键入十进制数并把它转换为二进制数 BX,然后用
BINHEX子程序把 BX中的二进制数以十六进制的形式显示输出。
为避免键入的十进制数和显示的十六进制数重叠,用子程序
CRLF实现回车换行的功能。
第 6章 子 程 序 设 计
1) 子程序调用方法说明子程序名,DECBIN。
功能:将键盘键入的十进制数转换为二进制数。
输入:从键盘键入十进制数。
输出,BX。
子程序调用:无。
子程序名,CRLF。
功能:回车、换行。
输入:无。
第 6章 子 程 序 设 计输出:在显示终端显示回车换行。
子程序调用:无。
子程序名,BINHEX。
功能:将 BX中的二进制数以十六进制的形式显示。
输入,BX。
输出:在显示终端显示十六进制数。
子程序调用:无。
第 6章 子 程 序 设 计
2) 子程序清单
DECBIN PROC NEAR
MOV BX,0
NEWCHAR,MOV AH,01
INT 21H
SUB AL,30H
JB EXIT
CMP AL,09
JA EXIT
CBW
XCHG AX,BX
MOV CX,10D
MUL CX
XCHG AX,BX
ADD BX,AX
JMP NEWCHAR
第 6章 子 程 序 设 计
EXIT,RET
DECBIN ENDP
CRLF PROC NEAR
MOV DL,0DH
MOV AH,02
INT 21H
MOV DL,0AH
MOV AH,02
INT 21H
RET
CRLF ENDP
BINHEX PROC NEAR
MOV CH,4
第 6章 子 程 序 设 计
AGAIN,MOV CL,4
ROL BX,CL
MOV AL,BL
AND AL,0FH
ADD AL,30H
CMP AL,3AH
JB DISPLAY
ADD AL,07H
DISPLAY,MOV DL,AL
MOV AH,02
INT 21H
DEC CH
JNZ AGAIN
RET
BINHEX ENDP
第 6章 子 程 序 设 计
6.4.2 子程序的调用例 6-10 编写十进制数转换成二进制数的程序。实现时采用调用例 6-9中的 DECBIN子程序,BINHEX和 CRLF子程序的方法。
CODE SEGMENT
MAIN PROC FAR
ASSUME CS∶CODE
START,PUSH DS
SUB AX,AX
PUSH AX
CALL DECBIN
第 6章 子 程 序 设 计
CALL CRLF
CALL BINHEX
RET
MAIN ENDP
CODE ENDS
END START
第 6章 子 程 序 设 计
6.5 子程序的参数传递方法
6.5.1 通过寄存器传递参数在主程序中,调用子程序前,将参数保存在某些通用寄存器中,子程序就可直接使用寄存器中的入口参数,80386/80486
的每个通用寄存器都可用于保存参数。同样,出口参数也可通过寄存器返回给主程序。用这种方法传递参数简单快捷,但需要占用通用寄存器。而寄存器数量十分有限,当要传递的参数较多时,采用寄存器传递就不行了,因此这种方法只适合于参数较少的情况。
第 6章 子 程 序 设 计例 6-11 编程实现将从键盘上输入的小写字母转换成大写字母后输出。
分析:由 ASCII码编码表可知,英文大、小写的 26个字母字符的编码值是顺序递增的,且各小写字母与相应大写字母之间的编码差值均为 32,因此当要将一个小写字母转换为大写字母时,只要将其 ASCII码值减去 32即可。为简化程序,将判断输入的字符是否为小写字母的工作编为一子程序,该子程序将判断的结果通过标志寄存器中的 CF标志返回给主程序,CF= 0表示是小写字母,CF= 1表示不是小写字母。主程序通过 AL寄存器将要判断的内容传递给子程序。
第 6章 子 程 序 设 计根据上述分析,编写的程序如下:
DATA SEGMENT
MSG DB 'ERROR! ',0DH,0AH,'$' ; 0DH为回车符,0AH换行符,$为字符串;结束符
DATA ENDS
CODE SEGMENT 'CODE'
ASSUME CS∶CODE,DS∶DATA
START PUSH CS
POP DS
INPOT,MOV AH,01H
INT 21H ;读入一字符,送到 AL,通过 AL传递参数给子程序
CALL COMPARE ;调用子程序 COMPARE
第 6章 子 程 序 设 计
JC ERR ;子程序通过 CF传递结果给主程序。 CF= 1,转 ERR
SUB AL,32 ; CF= 0,将小写转换为大写
MOV DL,AL
MOV AH,02
INT 21H ;输出转换后的大写字母
MOV AH,4CH
INT 21H ;返回 DOS
ERR:
MOV DX,OFFSET MSG
MOV AH,09
INT 21H ;输出出错信息,ERROR!,
JMP INPUT ;跳转到 INPUT,循环输入判断第 6章 子 程 序 设 计
COMPARE,;子程序
CMP AL,'a'
JB SETFLAG
CMP AL,'z'
JA SETFLAG
CLC
RET
SETFLAG:
STC
RET
CODE ENDS
END START
第 6章 子 程 序 设 计
6.5.2 用存储单元传递参数主程序与子程序之间可利用指定的存储单元传递参数,这样可使数据便于保存,而且适于参数较多的情况。采用这种方法时,要事先在内存中建立一个参数表,通过传送参数地址的方法实现调用程序和子程序间的参数传递。具体方法是先建立一个地址表,该表由参数地址构成,然后把表的首地址通过寄存器或堆栈传递给子程序。
第 6章 子 程 序 设 计例 6-12 用变址寻址的方法实现多精度加法运算。
STACK SEGMENT PARA STACK
DB 64 DUP('MYSTACK')
STACK ENDS
MYDATA SEGMENT PARA 'DATA'
TABLE DW 1234H,5678H,9ABCH,0DEF0H,111H,222H,333H;建立加数参数表
DW 4444H,5555H,6666H,7777H,8888H,9999H,0AAAAH
DW 0BBBBH,0CCCCH,0DDDDH,0EEEEH,0FFFFH
LSBANS DW?
MSBANS DW?
MYDATA ENDS
MYCODE SEGMENT PARA 'CODE' ;给宏汇编程序定义代码段第 6章 子 程 序 设 计
MYPROC PROC FAR ;过程被命名为 MYPROC(远过程 )
ASSUME CS∶MUCODE,DS∶MYDATA,SS∶STACK
PUSH DS ;保存 DS寄存器内容
SUB AX,AX ;给 AX寄存器清零
PUSH AX ;把零存到堆栈内
MOV AX,MYDATA ;把存储单元内容传送给 AX
MOV DS,AX ;把 AX内容放到 DS寄存器; 表中前 10个数的多精度加法,使用的是变址寻址
LEA BX,TABLE ;把表的基址偏移量放入 BX内
MOV SI,00H ;把变址置成零
MOV AX,TABLE[SI] ;得到第一个数
MOV CX,09H ;程序要进行 9次加法第 6章 子 程 序 设 计
AGAIN,ADD SI,02H ;变址值增 2
ADD AX,TABLE[SI] ;把下一个数加到和中
ADC DX,00H ;若有进位,则加到 DX寄存器
LOOP AGAIN ;若 CX不为零,则再加一个数
MOV LSBANS,AX ;把 AX内容传送进 LSBANS
MOV MSBANS,DX ;把 DX内容传送进 MSBANS;多精度加法到此结束
RET ;返回 DOS
MYPROC ENDP ;名为 MYPROC的过程到此结束
MYCODE ENDS ;名为 MYCODE的代码段到此结束
END ;整个程序结束第 6章 子 程 序 设 计
6.5.3 通过堆栈传递参数主程序和子程序可将要传送的信息放在栈中,使用时再从栈中弹出。由于栈具有先进后出、后进先出的特性,故多重调用中各重参数的层次很分明,很适于参数多且子程序有嵌套、递归调用的情况。前两种参数传递方法都不能实现递归调用的信息传送。
此时,主程序将参数入栈,子程序将参数从栈中弹出。由于在主程序中是先将参数入栈,然后才执行 CALL指令去调用相应子程序的,(这时,CALL指令将返回地址存于栈顶位置),因此在子程序中为了不破坏栈顶指针,不能直接用退栈指令 POP使参数弹出,
而经常借用 (E)BP寄存器来达到目的。
第 6章 子 程 序 设 计即将某一时刻的栈顶指针 (E)SP的值送给 (E)BP,使 (E)BP指向栈中某一位置,然后用地址表达式 [BP+ disp]间接访问栈的非栈顶字 (或字节 )单元内容,其中,disp是字 (或字节 )单元距离 (E)BP指向位置的相对位移量 (字节数 )。而子程序也可将返回参数存放在栈中由主程序预留的栈空间内,以便返回后主程序能从栈中弹出返回参数。
第 6章 子 程 序 设 计例 6-13 实现两个数组的分别求和。两个数组位于数据段中,将求和程序定义为过程,且主程序和过程分别安排在两个不同的代码段中 (即过程是 FAR类型的 )。
利用堆栈实现主程序向过程的参数传递,要特别注意配合好子程序中参数的读取和返回。
第 6章 子 程 序 设 计程序描述如下:
STACK SEGMENT PARA STACK 'STACK'
SPAE DW 20 DUP(? )
TOP EQU LENGTH SPAE
STACK ENDS
DATA SEGMENT
ARY1 DB 10 DUP(? ) ;定义数组 1
SUM1 DW?
ARY2 DB 100 DUP(? ) ;定义数组 2
SUM2 DW?
DATA ENDS
MAIN SEGMENT ;主程序段
ASSUME CS:MAIN,DS:DATA,SS:STACK
第 6章 子 程 序 设 计
CODE SEGMENT
MAIN PROC FAR
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 ; SUM过程的入口参数 2入栈
CALL SUM
MOV AX,SIZE ARY2
第 6章 子 程 序 设 计
PUSH AX
MOV AX,ARY2
PUSH AX
CALL SUM
RET
MAIN ENDP
CODE ENDS
PROCE SEGMENT ;过程段
ASSUME CS:PROCE,DS:DATA,SS:STACK
SUM PROC FAR
PUSH AX
PUSH BX
第 6章 子 程 序 设 计
PUSH CX
PUSH BP
MOV BP,SP
PUSHF
MOV CX,[BP+ 14] ;数组长度 SIZE由栈 → CX
MOV BX,[BP+ 12] ;数组存放的地址偏移量由栈 → BX
MOV AX,0
ADN,ADD AL,[BX]
ADC AH,0
INC BX
LOOP ADN
MOV [BX],AX ;数组之和送到结果区第 6章 子 程 序 设 计
POPF ;恢复现场
POP BP
POP CX
POP BX
POP AX
RET4 ;返回主程序,并废除参数 1和参数 2
SUM ENDP
PROCE ENDS
END MAIN
第 6章 子 程 序 设 计图 6-5 栈变化示意图参数 1
参数 2
CA L L
之 前
(S P)
参数 1
参数 2
CA L L
之 后
(S P)
CS
IP
参数 1
参数 2
现场保护之 后
(S P)
CS
IP
AX
BX
CX
BP
FL A G
参数 1
参数 2
现场恢复之 后
(S P)
CS
IP
RE T 4
以 后
(S P)
第 6章 子 程 序 设 计对于用栈传递参数,还要说明几点:
(1) 如果主程序没有为返回参数在栈中预留空间,则不能将它们压入栈,这时可采用寄存器或存储器将它们传送给主程序。
(2) 在 16位寻址方式中,近调用时,CALL指令只将返回地址的偏移量 (2个字节 )压入栈,而远调用时则是将返回的完整地址
(4个字节,段址及偏移地址各 2字节 ) 压入栈。在 32位寻址方式中,近调用向栈中压入 4个字节的返回偏移地址,远调用则压入 6
个字节 (2字节的段址和 4字节的偏移地址 )。
(3) 在使 (E)BP指向入口参数之前,一定要保护 (E)BP的原有值。
(4) 若调用程序和子程序在同一个模块 (源程序 )中,子程序可以直接访问模块中的变量。
第 6章 子 程 序 设 计例 6-14 实现数组求和功能。要求数组求和 (不考虑溢出情况 )由子程序实现,其数组元素及结果均为字符型数据。
STACKSG SEGMENT STACK 'STK'
DW 32 DUP('S')
STACKSG ENDS
DATA SEGMENT
ARY DW 1,2,3,4,5,6,7,8,9,10 ;定义数组
COUNT DW ($-ARY)/ 2 ;求数组元素个数
SUM DW? ;数组和的地址
DATA ENDS
CODE1 SEGMENT
第 6章 子 程 序 设 计
MAIN PROC FAR
ASSUME CS∶CODE1,DS∶DATA
PUSH DS
XOR AX,AX
PUSH AX
MOV AX,DATA
MOV DS,AX
CALL FAR PTRA ARY_SUM ;调用数组求和子程序
RET
MAIN ENDP
CODE1 ENDS
第 6章 子 程 序 设 计
CODE2 SEGMENT
ASSUME CS∶CODE2
ARY_SUMPROC FAR ;数组求和子程序
PUSH AX ;保存寄存器
PUSH CX
PUSH SI
LEA SI,ARY ;取数组起始地址
MOV CX,COUNT ;取元素个数
XOR AX,AX ;清 0累加器第 6章 子 程 序 设 计
NEXT,ADD AX,[SI] ;累加和
ADD SI,TYPE ARY ;修改地址指针
LOOP NEXT
MOV SUM,AX ;存和
POP SI ;恢复寄存器
POP CX
POP AX
RET
ARY_SUM ENDP
CODE2 ENDS
END MAIN
第 6章 子 程 序 设 计
6.6 子程序的嵌套与递归
6.6.1 子程序的嵌套调用例 6-15 编制子程序,将二进制数转换为十进制数显示输出。
分析:在进行算术运算时,常常要将二进制的结果转换成十进制的形式显示或打印输出。设将 BX寄存器中的二进制数转换为十进制数显示输出。
要将 BX寄存器中的二进制数转换为十进制数显示输出,应计算出 BX中的二进制数里含有多少个 10000D(即 2710H),多少个
1000D(即 03E8H),多少个 100D(即 0064H),多少个 10D(即 000AH),
多少个 1,再把这些数拼上 30H使之变成对应的 ASCII码,再一位一位地显示。
第 6章 子 程 序 设 计
1.子程序调用方法说明子程序名,BINADECI
功 能:将 16位无符号二进制数转换成十进制数显示输出,原数保持不变入口参数,BX中存放待转换的二进制数出口参数:显示输出所用寄存器,CX,DL寄存器调用形式:调用本子程序之前,将待转换的无符号二进制数送至 BX寄存器第 6章 子 程 序 设 计
2.子程序清单
CODE_SEG SEGMENT
BINADECI PROC FAR
ASSUME CS∶CODE_SEG
PUSH CX ;保护现场
PUSH DX
PUSH BX
MOV CX,2710H
CALL DISPDECI
MOV CX,03E8H
CALL DISPDECI
MOV CX,0064H
CALL DISPDECI
第 6章 子 程 序 设 计
MOV CX,000AH
CALL DISPDECI
MOV CX,0001H
CALL DISPDECI
POP BX ;恢复现场
POP DX
POP CX
RET
BINADECI ENDP
DISPDECI PROC NEAR
MOV DL,0
AGAIN,CMP BX,CX
第 6章 子 程 序 设 计
JB EXIT
INC DL
SUB BX,CX
JMP AGAIN
EXIT,ADD DL,30H
MOV AH,02
INT 21H
RET
DISPDECI ENDP
CODE_SEG ENDS
第 6章 子 程 序 设 计这是一个嵌套子程序,即 BINADECI又调用子程序 DISPDECI。在子程序 DISPDECI中没有采用除法的方法,而采用的是减法的方法。
再编制一个主程序,调用并执行这个子程序。主程序描述如下:
CODE SEGMENT
MAIN PROC FAR
ASSUME CS∶CODE
START,PUSH DS
XOR AX,AX
PUSH AX
CALL CRLF
第 6章 子 程 序 设 计
MOV BX,0000011111010011B
CALL BINADECI
CALL CRLF
RET
MAIN ENDP
CRLF PROC NEAR
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
CRLF ENDP
CODE ENDS
END START
第 6章 子 程 序 设 计
6.6.2 子程序的递归调用例 6-16 编制计算 N!(N≥0) 的程序。
阶乘函数递归定义如下:
1 当 N= 0时
N!=
N× (N-1)! 当 N>0时分析:可用递归定义来设计该程序。
第 6章 子 程 序 设 计计算 N!本身是一个子程序,由于 N!= N× (N-1)!,所以为了计算 (N-1)!又递归调用 N!的子程序,只不过每次调用时使用的参数不同而已。递归子程序的设计必须保证每次调用都不破坏以前调用时所用的参数和中间结果;为保证每次调用都能正确无误,一般把每次调用的参数、有关寄存器的内容以及中间结果入栈保存。递归子程序中还必须包括基数的设置,当调用参数达到基数时必须有一条条件转移指令实现嵌套退出,保证能按反向次序退出并计算 N!后返回主程序。
第 6章 子 程 序 设 计
DATA SEGMENT
N DW?
RESULT DW?
DATA ENDS
STACK SEGMENT
DW 100 DUP(0)
TOP LABEL WORD
STACK ENDS
CODE SEGMENT
MAIN PROC FAR
ASSUME CS∶CODE,DS∶DATA,SS∶STACK
第 6章 子 程 序 设 计
START,MOV AX,STACK
MOV SS,AX
MOV SP,OFFSET TOP
PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV AX,N
PUSH AX
CALL FACT
ADDRL,POP RESULT
RET
第 6章 子 程 序 设 计
MAIN ENDP
FACT PROC NEAR
PUSH BP
MOV BP,SP
MOV AX,[BP+4]
CMP AX,0
JNE FACTL
INC AX
JMP EXIT
第 6章 子 程 序 设 计
FACTL,DEC AX
PUSH AX
CAIL FACT
ADDR2,POP AX
MUL [BP+4]
EXIT,MOV [BP+4],AX
POP BP
RET
FACT ENDP
CODE ENDS
END START
第 6章 子 程 序 设 计在程序执行的过程中,子程序 FACT不断地调用自己,每调用一次就把 (N-1)及有关信息入栈保存,直到 N等于基数时为止。
当 N等于 0时开始返回,程序在不断返回的过程中,计算 (N-
1)× N并保存中间结果直到 N等于给定值为止 (本例中假设 N≤8) 。
当 N= 3时,栈状态如图 6-6所示。
第 6章 子 程 序 设 计
( BP )
( BP )
( BP )
( BP )
原 ( BP )
A D D R2
N = 0
原 ( BP )
A D D R2
N = 1
原 ( BP )
A D D R2
N = 2
原 ( BP )
A D D R2
N = 3
图
6-
6
N=
3
时栈状态示意图第 6章 子 程 序 设 计
6.7 综 合 举 例例 6-17 接收键盘输入的数据,然后将输入的数据在内存中传送到另一区域里,并显示在终端上。
分析:首先将传送的源区定义在内存的低地址处,源区的首地址命名为 AREA1,将目的区定义在内存的高地址处,目的区的首地址命名为 AREA2,这样就可以确定数据是从低地址处向高地址处传送。在传送数据时还需要考虑源区与目的区的重叠情况,即源区与目的区不互相重叠以及源区与目的区相互重叠,
这两种情况下传送的方式是不一样的。
第 6章 子 程 序 设 计程序一:源区与目的区不互相重叠
DATA SEGMENT ;定义数据段
HELP1 DB 'ENTER THE CHAR THAT WANTED TO BE MOVED(NUMBER BELOW 50):$'
HELP2 DB 'AFTER MOVING,THE RESULT IS:$'
AREA1 DB 52 ;定义源区
DB?
DB 52 DUP(?)
AREA2 DB 52 DUP('? ') ;定义目的区
DATA ENDS
CODE SEGMENT ;定义代码段
ASSUME CS:CODE,DS:DATA
第 6章 子 程 序 设 计
START,MOV AX,DATA
MOV DS,AX
MOV DX,OFFSET HELP1 ;提示输入数据
MOV AH,09H
INT 21H
MOV DL,0AH ;输出换行符
MOV AH,02
INT 21H
MOV DL,0DH ;输出回车符
MOV AH,02
INT 21H
MOV DX,OFFSET AREA1 ;将接收的数据放在源区
MOV AH,0AH
INT 21H
第 6章 子 程 序 设 计
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
MOV SI,OFFSET AREA1+1
MOV CL,[SI] ;设置循环次数
XOR CH,CH
MOV SI,OFFSET AREA1+2
MOV DI,OFFSET AREA2
CLD ;由低地址处向高地址处进行传送第 6章 子 程 序 设 计
AGAIN,MOV AL,[SI] ;开始传送
MOV [DI],AL
INC DI
INC SI
DEC CL
JNZ AGAIN ;计数器为 0时传送完毕
MOV AL,'$'
MOV [DI],AL
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
MOV DX,OFFSET HELP2 ;提示要输出数据第 6章 子 程 序 设 计
MOV AH,09H
INT 21H
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
MOV DX,OFFSET AREA2 ;输出传送后的数据
MOV AH,09H
INT 21H
MOV AH,4CH ;返回 DOS
INT 21H
CODE ENDS
END START
第 6章 子 程 序 设 计程序二:源区与目的区相互重叠
DATA SEGMENT ;定义数据段
HELP1 DB 'ENTER THE CHAR THAT WANTED TO BE MOVED(NUMBER BELOW 50):$'
HELP2 DB 'AFTER MOVING,THE RESULT IS:$'
AREA1 DB 50
DB?
DB 50 DUP(?)
AREA2 EQU AREA1+5
N EQU 50
DATA ENDS
STACK SEGMENT PARA STACK 'STACK' ;定义堆栈段
TEMP DB 50 DUP(?)
第 6章 子 程 序 设 计
STACK ENDS
CODE SEGMENT ;定义代码段
ASSUME CS:CODE,DS:DATA
ASSUME ES:DATA,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV DX,OFFSET HELP1 ;提示输入数据
MOV AH,09H
INT 21H
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
第 6章 子 程 序 设 计
MOV AH,02
INT 21H
MOV DX,OFFSET AREA1 ;将接收的数据放在源区
MOV AH,0AH
INT 21H
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
MOV SI,OFFSET AREA1+1 ;确定循环次数
MOV CL,[SI]
XOR CH,CH
第 6章 子 程 序 设 计
MOV SI,OFFSET AREA1+2
MOV DI,OFFSET AREA2
STD ;数据块从后向前传送
ADD SI,CX
ADD DI,CX
MOV AX,'$' ;把字符 '$' 放在最后一个字符后面
MOV [DI],AX
DEC SI ;确定从源区传送的首地址
DEC DI ;确定目的区的首地址
REP MOVSB ;传送数据
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
第 6章 子 程 序 设 计
INT 21H
MOV DX,OFFSET HELP2 ;提示输出数据
MOV AH,09H
INT 21H
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
MOV DX,OFFSET AREA2 ;输出传送完后的数据
MOV AH,09H
INT 21H
MOV AH,4CH ;返回 DOS
INT 21H
CODE ENDS
END START
第 6章 子 程 序 设 计讨论:
(1) 关于数据从高地址处向低地址处传送的问题。当源区定义在高地址处,目的区定义在低地址处时,传送方法正好与数据从低地址端向高地址处传送的方法相反。当源区与目的区不互相重叠时,按照减量的方向使用循环结构从后向前地将数据传送到目的区;当源区与目的区相互重叠,可以按照增量的方向使用循环结构从前向后地将数据传送到目的区。
(2) 实际操作时,源区与目的区是否重叠的判断方法有两种。
① 当数据从低地址处向高地址处传送时,判断的方法是看源区内数据块的最后一个单元的地址是否比目的区的首地址小,
若是,则源区与目的区不重叠,否则,源区与目的区重叠。具体实现时,只需将循环传送部分的代码换为如下代码段即可。
第 6章 子 程 序 设 计
MOV CX,N
MOV SI,OFFSET AREA1
MOV DI,OFFSET AREA2
CLD
PUSH SI
ADD SI,N-1
CMP SI,DI
POP SI
JL OK
STD
ADD SI,N-1
ADD DI,N-1
OK,REP MOVSB
第 6章 子 程 序 设 计其中,假定源区内的数据为有符号数,且源区的首地址先送寄存器 SI,目的区的首地址送寄存器 DI,N为源区内数据块长度。
② 当数据从高地址处向低地址处传送时,判断的方法是看传送后目的区内数据块最后一个单元的地址是否比源区的首地址小,若是,则源区与目的区不重叠;否则,源区与目的区重叠。具体实现时,只需将循环传送部分的代码换为如下代码段即可。
第 6章 子 程 序 设 计
MOV CX,N
MOV SI,OFFSET AREA1
MOV DI,OFFSET AREA2
CLD
PUSH DI
ADD DI,N-1
CMP DI,SI
POP DI
JG OK
STD
ADD SI,N-1
ADD DI,N-1
OK,REP MOVSB
第 6章 子 程 序 设 计其中,假定源区内的数据为有符号数,且源区的首地址先送寄存器 SI,目的区的首地址送寄存器 DI,N为源区内数据块长度。
例 6-18 将输入的 10个数转换为二进制数进行比较排序,
最后将排序后的结果显示在终端上。
分析:首先将输入的 10个数据循环读入,调用转换子程序
GETD将每一个数转化为二进制数并依次存放在以 RESULT开始的内存单元中,再调用排序的子程序 CAMP将放在数据缓冲区内的数据进行排序,最后调用输出的子程序 PTDN,将二进制数转化为 ASCII码输出。
第 6章 子 程 序 设 计
DATA SEGMENT ;定义数据段
BUF DB 8 ;存放 ASCII码
DB?
DB 8 DUP(?)
CHANGE DB 5 DUP(?) ;存放 BCD码
RESULT DW 10 DUP(?) ;存放二进制数
DISP DB 10 DUP(?) ;存放转换后的 ASCII码
SIGN DB? ;标志位
HELP1 DB 'ENTER THE NUMBER:$ ' ;提示符
HELP2 DB 'THE RESULT IS,$'
DATA ENDS
STACK SEGMENT PARA STACK 'STACK' ;定义堆栈段
DB 100 DUP(?)
第 6章 子 程 序 设 计
STACK ENDS
CODE SEGMENT ;代码段
ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
START PROC FAR
PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV DX,OFFSET HELP1 ;提示输出
MOV AH,09H
INT 21H
MOV BX,OFFSET RESULT
MOV CX,10 ;循环转换输入的 10个数第 6章 子 程 序 设 计
AGAIN,PUSH CX
MOV DL,0AH
MOV AH,2
INT 21H
MOV DL,0DH
MOV AH,2
INT 21H
CALL GETD
MOV [BX],CX ;将转换后的二进制数存放在 RESULT开头的单元中
INC BX
INC BX
POP CX
第 6章 子 程 序 设 计
LOOP AGAIN
CALL CAMP ;将 RESULT单元中的数排序
MOV DL,0AH
MOV AH,2
INT 21H
MOV DL,0DH
MOV AH,2
INT 21H
MOV DX,OFFSET HELP2 ;提示输出
MOV AH,09H
INT 21H
MOV CX,10
MOV SI,OFFSET RESULT
第 6章 子 程 序 设 计
LOP4,PUSH CX
MOV CX,[SI]
CALL PTDN ;输出
INC SI
INC SI
POP CX
LOOP LOP4
RET
START ENDP;输入十进制数,并将其转换成二进制数的子程序;入口参数:键盘输入数据;出口参数,CX
第 6章 子 程 序 设 计
GETD PROC
PUSH AX
PUSH BX
PUSH DX
PUSH SI
PUSH DI
MOV SI,OFFSET BUF+1 ;输入每个数的字符数
MOV DI,OFFSET CHANGE
PUSH DI
MOV DX,OFFSET BUF
MOV AH,10
INT 21H ;接收键盘输入
MOV BL,[SI] ;实际输入的字符个数送 BL
DEC BL
第 6章 子 程 序 设 计;记录符号位
INC SI
MOV AL,0
MOV SIGN,AL ;符号单元送 0
MOV AL,[SI] ;取实际符号位
CMP AL,'+'
JZ NEXT1 ;若实际符号位为负
MOV AL,1 ;则符号单元送 1
MOV SIGN,AL; ASCII码转换成 BCD码
NEXT1,PUSH BX ;保存字符个数
NEXT2,INC SI
MOV AL,[SI]
AND AL,0FH
第 6章 子 程 序 设 计
MOV [DI],AL ; ASCII码转换成 BCD码送到 CHANG开始的单元
INC DI
DEC BL ;字符个数减 1
JNZ NEXT2 ;若不等于 0,继续转换
POP BX; BCD码转换成二进制码
POP DI
MOV CX,0 ;累加和置初值
AG1,PUSH BX
ADD CX,CX
MOV BX,CX
ADD CX,CX
ADD CX,CX
ADD CX,BX ;累加和 *10
第 6章 子 程 序 设 计
MOV BL,[DI]
MOV BH,0
INC DI
ADD CX,BX ;累加和 *10后加下一个 BCD数字
POP BX
DEC BL
JNE AG1 ;未转换完时继续
MOV AL,SIGN
OR AL,AL ;查看符号位
JZ DONE
NEG CX ;若为负数求补
DONE,POP DI
第 6章 子 程 序 设 计
POP SI
POP DX
POP BX
POP AX
RET
GETD ENDP;将数据缓冲区内的数据按照升序的顺序排列的子程序;入口参数:缓冲区内的数据;出口参数:排好后的缓冲区的数据
CAMP PROC
PUSH AX ;保护现场
PUSH BX
PUSH CX
第 6章 子 程 序 设 计
PUSH DX
PUSH SI
MOV DX,9
LOP1,MOV SI,OFFSET RESULT ;外循环
MOV CX,DX
MOV BL,0
LOP2,MOV AX,[SI] ;定义内循环
CMP AX,[SI+2]
JGE LOP3
XCHG AX,[SI+2]
MOV [SI],AX
OR BL,1
第 6章 子 程 序 设 计
LOP3,INC SI
INC SI
DEC CX
JNZ LOP2
AND BL,BL
JZ EXIT ;循环结束
DEC DX
JNZ LOP1
EXIT,POP SI
POP DX
POP CX
POP BX
POP AX ;恢复现场
RET
第 6章 子 程 序 设 计
CAMP ENDP;输出 16位二进制带符号数的子程序;入口参数,CX中为待输出的数据;所用子程序,QUAN
PTDN PROC
PUSH AX
PUSH BX
PUSH DX
PUSH SI ;保护现场
MOV BX,OFFSET DISP ; BX指向缓冲区
MOV AL,20H ;输出的数据间需要有空格符
MOV [BX],AL
INC BX
第 6章 子 程 序 设 计
MOV AL,CH
OR AL,AL ;查看符号位
JNS PLUS ;正数转 PLUS
NEG CX ;负数求补
MOV AL,'-'
MOV [BX],AL ;缓冲区存入负号
JMP GOON
PLUS,MOV AL,'+'
MOV [BX],AL ;缓冲区存入正号
GOON,MOV AL,0
PUSH AX
第 6章 子 程 序 设 计
INC BX
MOV SI,10000
CALL QUAN ;求万位数,并转换为 ASCII码存入缓冲区
MOV SI,1000
CALL QUAN ;求千位数
MOV SI,100
CALL QUAN ;求百位数
MOV SI,10
CALL QUAN ;求十位数
POP AX
MOV AL,30H
ADD AL,CL
MOV [BX],AL ;求个位数
INC BX
MOV AL,'$' ; $送入缓冲区尾,9号功能调用所要求
MOV [BX],AL
第 6章 子 程 序 设 计
INC BX
MOV AL,0AH ;换行以及回车
MOV [BX],AL
INC BX
MOV AL,0DH
MOV [BX],AL
MOV DX,OFFSET DISP
MOV AH,9 ; 9号功能调用输出显示
INT 21H
POP SI
POP DX
POP BX ;恢复现场
POP AX
RET
PTDN ENDP
第 6章 子 程 序 设 计;统计 CX寄存器所包含权 (在 SI中 )的个数并将其转换成 ASCII码的子程序;入口参数,SI为权码,CX中为给定的数,BX指向 ASCII码结果缓冲区;出口参数:结果缓冲区的当前单元存放转换完的 ASCII码,并且调整 BX
指向缓冲区的下一个单元
QUAN PROC
MOV DL,0 ; DL存放权的个数,初值 0
AI,SUB CX,SI
JC DOWN ;不够减转 DOWN
INC DL ;够减 DL加 1
POP AX
OR AL,1
PUSH AX
JMP AI ;再减权第 6章 子 程 序 设 计
DOWN,ADD CX,SI ;恢复 CX
POP AX
PUSH AX
CMP AL,1
JZ ED
CMP DL,0
JZ EN
ED,MOV AL,30H
ADD AL,DL
MOV [BX],AL ;转 ASCII码并存入缓冲区
INC BX ;指针调整
EN,RET
QUAN ENDP
CODE ENDS
END START
第 6章 子 程 序 设 计讨论:
(1) 排序的优化。源程序中排序子程序使用的是冒泡排序方法,并且使用的是例 5-18给出的优化的冒泡排序法,即比较的轮数是动态的,当到达某一轮后,如果已排好 (标志位没有变化 )则跳出循环,另一方面每一轮中的比较次数是动态的,每一轮比较的次数应该是上一轮的次数减 1,即轮数依次减 1。
(2) 数据范围的限制。本程序只能处理 -32 768~ +32 767
之间的数 (包括界限值 ),如果比较的数不在这个范围中,则需要将程序进行拓展,即将存储二进制数的内存单元定义为双字单元,在数据转换时使用的是寄存器进行运算,在这中间需要注意进位的问题。
第 6章 子 程 序 设 计
(3) 排序算法。该源程序是将输入数据进行降序排列,如果需要变成升序排列,则将子程序 CAMP中的有符号数条件转移指令 JGE变为 JLE即可。
(4) 输入的特殊情况。当输入为 0时,程序会当作只输入一个,-”号来处理,这时在判断输入数据的符号时,需再加一条判断指令:
CMP AL,0
JZ NEXT3
第 6章 子 程 序 设 计
NEXT3后面的语句为 MOV CX,0,在输出时还要加上判断是
0的指令:
CMP CX,0
JZ GO
GO,MOV DL,30H
MOV AH,2
INT 21H
如果在输入正数时不想输入,+”号,那么还需要加判断指令,
即判断输入的第一个字符是否在 1~ 9之间,然后转到 NEXT2。
第 6章 子 程 序 设 计例 6-19 输入两个数 X,Y,在屏幕上显示 xy的值。
分析:首先,要将从屏幕上接收的 x,y的值的 ASCII码转化为二进制数,以便于进一步的运算;其次,判断 x,y是否为 0,若
x=0,则不论 y为何值结果都是 0,若 y=0,则不论 x为何值结果都为 1,这样可直接输出结果,若 x,y均不为 0,则用二重循环的加法运算求得 的值;再次,判断 y是否大于 0,若 y>0,当 x>0
或 x<0且 y为偶数时,,当 x<0且 y为奇数时,;
若 y<0,当 x>0或 x<0且 y为偶数时,?,?当 x<0且 y为奇数时,。 最后,将转化为 ASCII码输出 。
YX
YY XX? YY XX
Y
Y
X
1X?
Y
Y
X
1X
第 6章 子 程 序 设 计
DATA SEGMENT ;定义数据段
BUF1 DB 8 ;存放 X的 ASCII码
DB?
DB 8 DUP(?)
BUF2 DB 8 ;存放 Y的 ASCII码
DB?
DB 8 DUP(?)
CHANGEDB 5 DUP(?) ;存放 BCD码
TEMP DW? ;存放 Y 的绝对值的二进制数
DISP DB 15 DUP(?) ;存放结果的 ASCII码
SIGN1 DB? ; X的符号位
SIGN2 DB? ; Y的符号位第 6章 子 程 序 设 计
HELP1 DB 'PLEASE ENTER THE VALUE OF X,$ ' ;提示符
HELP2 DB 0AH,0DH,'PLEASE ENTER THE VALUE OF Y,$ '
HELP3 DB 0AH,0DH,'THE RESULT IS,$ ',0AH,0DH,'$'
HELP4 DB ')^($ '
HELP5 DB 0AH,0DH,'WARNING:OVER THE DISTINCT!$ ' ;出错提示信息
DATA ENDS
STACK SEGMENT PARA STACK 'STACK' ;定义堆栈段
DB 100 DUP(?)
STACK ENDS
CODE SEGMENT ;代码段
ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
第 6章 子 程 序 设 计
START,MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV DX,OFFSET HELP1 ;提示输入
MOV AH,09H
INT 21H
MOV DX,OFFSET BUF1 ;接收 X的值
MOV AH,0AH
INT 21H
XOR AX,AX
MOV SI,OFFSET BUF1+1
MOV AL,[SI]
ADD SI,AX
INC SI
第 6章 子 程 序 设 计
MOV AL,'$' ; BUF1缓冲区结尾加 $
MOV [SI],AL
MOV SI,OFFSET BUF1+2
MOV AL,[SI]
CMP AL,'0' ;判断 X是否为 0
JNZ NEXT1
INC SI
MOV AL,'$'
MOV [SI],AL
MOV SI,OFFSET DISP ; DISP缓冲区写入 0
MOV AL,30H
MOV [SI],AL
第 6章 子 程 序 设 计
INC SI
MOV AL,'$'
MOV [SI],AL
JMP NEXT2
NEXT1,MOV SI,OFFSET BUF1+1
MOV DI,OFFSET SIGN1
CALL GETD
PUSH CX ; X绝对值的二进制数入栈
NEXT2,MOV DX,OFFSET HELP2 ;提示输入
MOV AH,09H
INT 21H
MOV DX,OFFSET BUF2 ;接收 Y的值
MOV AH,0AH
INT 21H
第 6章 子 程 序 设 计
XOR AX,AX
MOV SI,OFFSET BUF2+1 ; BUF1缓冲区结尾加 $
MOV AL,[SI]
ADD SI,AX
INC SI
MOV AL,'$'
MOV [SI],AL
MOV SI,OFFSET DISP
MOV AL,[SI]
CMP AL,'0' ; X是否已经为 0
JZ DIS
MOV SI,OFFSET BUF2+2
MOV AL,[SI]
CMP AL,'0' ; Y是否等于 0
第 6章 子 程 序 设 计
JNZ NEXT3
INC SI
MOV AL,'$'
MOV [SI],AL
MOV SI,OFFSET DISP
MOV AL,31H
MOV [SI],AL
INC SI
MOV AL,'$'
MOV [SI],AL
JMP DIS
NEXT3,MOV SI,OFFSET BUF2+1
MOV DI,OFFSET SIGN2
第 6章 子 程 序 设 计
CALL GETD
MOV TEMP,CX ; Y绝对值的二进制数送临时缓冲区 TEMP
MOV DX,CX ; DX中为 Y绝对值的二进制数
POP CX
MOV AX,CX ; AX,CX中为 X绝对值的二进制数
CALL FUNC
MOV CX,AX ;的值送 CX
MOV AL,SIGN2
CMP AL,1 ; Y是否为负数
JZ NEXT4
CALL PTDN
JMP DIS
NEXT4,CALL NEGT
第 6章 子 程 序 设 计
DIS,MOV DX,OFFSET HELP3 ;提示输出
MOV AH,09H
INT 21H
MOV DL,' ('
MOV AH,02H
INT 21H
MOV DX,OFFSET BUF1+2 ;显示 X的值
MOV AH,09H
INT 21H
MOV DX,OFFSET HELP4
MOV AH,09H
INT 21H
MOV DX,OFFSET BUF2+2 ;显示 Y的值
MOV AH,09H
INT 21H
第 6章 子 程 序 设 计
MOV DL,') '
MOV AH,02H
INT 21H
MOV DL,'='
MOV AH,02H
INT 21H
MOV DX,OFFSET DISP ;显示最终结果
MOV AH,09H
INT 21H
EXIT,MOV AH,4CH
INT 21H;将 X和 Y的绝对值转换成二进制数并将符号位存入 DI指向单元的子程序;入口参数,SI,DI;出口参数,CX
第 6章 子 程 序 设 计
GETD PROC
PUSH AX
PUSH BX
PUSH DX
MOV BL,[SI] ;实际输入的字符个数送 BL
INC SI
MOV AL,[SI] ;判断是否有符号位
CMP AL,'-'
JNZ GO1 ;若实际符号位为负
MOV AL,1 ;则符号单元送 1
MOV [DI],AL
DEC BL
INC SI; ASCII码转换成 BCD码第 6章 子 程 序 设 计
GO1,MOV DI,OFFSET CHANGE
PUSH DI
PUSH BX ;保存字符个数
GO2,MOV AL,[SI]
AND AL,0FH
MOV [DI],AL ; ASCII码转换成 BCD码送到 CHANGE开始的单元
INC DI
INC SI
DEC BL ;字符个数减 1
JNZ GO2 ;若不等于 0,继续转换
POP BX; BCD码转换成二进制码
POP DI
MOV CX,0 ;累加和置初值第 6章 子 程 序 设 计
AG1,PUSH BX
ADD CX,CX
MOV BX,CX
ADD CX,CX
ADD CX,CX
ADD CX,BX ;累加和 *10
MOV BL,[DI]
MOV BH,0
INC DI
ADD CX,BX ;累加和 *10后加下一个 BCD数字
POP BX
DEC BL
JNE AG1 ;未转换完时继续
POP DX
POP BX
POP AX
RET
第 6章 子 程 序 设 计
GETD ENDP; X和 Y转换成二进制数后求 |X||Y|的子程序;入口参数,AX,CX,DX;出口参数,AX
FUNC PROC
PUSH BX ;保护现场
PUSH SI
PUSH DI
PUSH CX
LOP1,POP CX
PUSH CX
MOV BX,AX
DEC DX
JZ EN ;外层循环是否结束第 6章 子 程 序 设 计
LOP2,DEC CX
JZ LOP1 ;内层循环是否结束
ADD AX,BX ;累加
JNO GG ;判断是否溢出
MOV DX,OFFSET HELP5 ;溢出提示信息
MOV AH,09H
INT 21H
JMP EXIT
GG,JMP LOP2
EN,POP CX ;恢复现场
POP DI
POP SI
POP BX
RET
FUNC ENDP
第 6章 子 程 序 设 计; Y>0时 XY的 ASCII码存入 DISP开头缓冲区内的子程序;入口参数,CX中为待处理的数据;出口参数:结果的 ASCII码存入 DISP开头的缓冲区中;所用子程序,QUAN
显示程序用 PTDN。子程序 PTDN和 QUAN与例 6-18中的 PTDN和 QUAN完全一样,
这里就不再赘述了,请参见例 6-18的描述; Y<0时 XY的 ASCII码存入 DISP开头缓冲区内的子程序;入口参数,CX中为待处理的数据;出口参数:结果的 ASCII码存入 DISP开头的缓冲区中第 6章 子 程 序 设 计
NEGT PROC
PUSH AX
PUSH BX
PUSH DX
PUSH SI
PUSH DI ;保护现场
MOV DI,OFFSET DISP ; DI指向缓冲区 DISP
MOV AL,SIGN1
CMP AL,1 ; X是否为负数
JNZ NU
MOV SI,OFFSET TEMP
MOV DX,[SI]
CLC
RCR DX,1 ; Y是否为偶数
JNC NU
MOV AL,'-'
MOV [DI],AL ;缓冲区存入负号
INC DI
第 6章 子 程 序 设 计
NU,MOV AL,30H
MOV [DI],AL
INC DI
MOV AL,','
MOV [DI],AL
INC DI
MOV BL,0
MOV SI,CX
MOV CX,10 ;转换精度
MOV AX,1 ;求倒数的第一个被除数为 1
PUSH BX
第 6章 子 程 序 设 计
LO,PUSH CX
ADD AX,AX
MOV BX,AX
ADD AX,AX
ADD AX,AX
ADD AX,BX ;被除数乘 10
MOV CL,0 ; CL存放权的个数,初值 0
TN,SUB AX,SI
JC DOWN ;乘完减除数,不够减转 DOWN
INC CL ;够减 CL加 1
POP DX
POP BX
OR BL,1 ;有一个权的个数已经不为 0
PUSH BX
PUSH DX
JMP TN
第 6章 子 程 序 设 计
DOWN,ADD AX,SI ;恢复 AX
ADD CL,30H
MOV [DI],CL ;将权值的 ASCII码存入缓冲区
INC DI
CMP AX,0 ;已经除尽
JZ EX
POP CX
LOOP LO
POP BX
CMP BL,0 ;是否有至少一个权的个数不为 0
JNZ EX
MOV DI,OFFSET DISP ;如果所有的权的个数都为 0,则显示 0
MOV AL,30H
MOV [DI],AL
INC DI
第 6章 子 程 序 设 计
EX,MOV AL,'$'
MOV [DI],AL
POP DI
POP SI
POP DX
POP BX
POP AX ;恢复现场
RET
NEGT ENDP
CODE ENDS
END START
第 6章 子 程 序 设 计讨论:
(1) 特殊情况的处理。如果 X=0,Y为负数,则结果应该趋于无穷大,而本程序的结果为 0。如果要将这一特殊的与实际不符的情况排除,应该在判断 X=0后跳到如下代码:
MOV SI,OFFSET BUF2+2
MOV AL,[SI]
CMP AL,'-'
JNZ DIS
MOV DI,OFFSET DISP
MOV AL,'-'
MOV [DI],AL
INC DI
第 6章 子 程 序 设 计
MOV AL,'>'
MOV [DI],AL
INC DI
MOV AL,'$'
MOV [DI],AL
MOV DX,DISP
MOV 09H
INT 21H
MOV DX,HELP6
MOV 09H
INT 21H
HLT
如果要将上述程序段加入的话,应先在数据段增加定义:
HELP6 DB 'INFINITY$'。
第 6章 子 程 序 设 计
(2) 用户误操作的处理。如果用户输入的不是数字也不是
‘ -’号,则程序应该予以提示,要完成此功能应该增加如下代码:
MOV SI,OFFSET BUF1
MOV CX,[SI+1]
ADD SI,2
LP,CMP [SI],30H
JBE GO1
CMP [SI],39H
JAE GO2
第 6章 子 程 序 设 计
GO1,CMP [SI],28H
JNZ GO2
INC SI
LOOP LP
JMP NT
GO2,MOV DX,OFFSET HELP7
MOV AH,09H
INT 21H
HLT
第 6章 子 程 序 设 计如果要将上述程序段加入的话,应先在数据段增加定义:
HELP7 DB 'INPUT ERROR! $';此段代码应放在用 10号功能调用读入 X的值的代码后面,如果要判断 Y的输入,只需将代码中的 BUF1改为 BUF2;标号 NT 指向在源程序中 10号功能调用读入
X值的下一条代码。
第 6章 子 程 序 设 计
(3) 局限性。本程序只能处理 在 -32 768~ +32 767之间的情况,如果超出这个范围则显示 WARNING:OVER THE
DISTINCT!。如果需要将结果范围拓展则需要进一步进行判断。
(4) 精度的取法。当 X不等于 0且 Y小于 0时,结果应该为小数。本程序对于小数精度的处理方法是:精度取为 10,第 11位舍掉,如果用户想对第 11位四舍五入,只需将 10次的减法改为
11次,判断第 11位如果大于 5,则将第 10位的数加 1,再在第 11
位所处空间的内容后添入 $;如果第 11位小于 5,则直接将第 11
位所处空间的内容后添入 $。
YX
第 6章 子 程 序 设 计
(5) 标志位的设置。本程序中有一关键性的技巧,那就是标志位的设置。在程序中,标志位共出现两次:一次是在 QUAN
子程序中,BH为标志位,用处是看权值由 10000变为 10的过程中是不是所有的权的个数都为 0,如果 BH一为 0,则送 30H到 DI所指的地址;如果不是,则按普通情况处理;另一次是在 NEGT子程序中,BL为标志位,用处是看小数的每一位是不是都是 0,如果是,则直接将 30H写入 DI所指的地址,而不写入 0000000000的
ASCII码。
第 6章 子 程 序 设 计习 题 六
6.1 编程序重复接收学生分数 (用 A,B,C,D表示 )且自动汇总各种分数的人数,并输出汇总结果。若按下 &键,表示输入结束。按下 A,B,C,D以外的任一键都无效,并提示 ERROR。
(使用子程序编制,各段定义要求完整 )。
6.2 子程序文件说明中应包括哪几方面的内容?为什么?
6.3 编程序一边从键盘上接收字符,一边将其写入指定的文件,字符个数共 60,并将其显示在屏幕上。
第 6章 子 程 序 设 计
6.4 编写子程序实现两个多字节未压缩 BCD码相乘。
入口参数,SI指向乘数首地址,DI指向被乘数首地址,BX
指向乘积首地址,乘数的长度由 OPRD指定。
出口参数,SI指向乘数首地址,DI指向被乘数首地址,BX
指向乘积首地址。
第 6章 子 程 序 设 计
6.5 用子程序编制求解下列问题的程序:
(1) 将两个多字节压缩 BCD码相加,并把结果显示出来;
(2) 将字节单元中的二进制数转换成十进制数输出;
(3) 在字符串中第 n个字符前插入一个字符;
(4) 删除字符串中从第 n个字符开始的 m个字符;
(5) 实现若干有符号数的排序操作。
6.1 子程序的概念与特性
6.2 子程序的结构形式
6.3 子程序调用和返回指令
6.4 子程序的设计
6.5 子程序的参数传递方法
6.6 子程序的嵌套与递归
6.7 综合举例第 6章 子 程 序 设 计
6.1 子程序的概念与特性
6.1.1 子程序的概念人们在编写程序时,常常会遇到这样的情况:某些完成相同的功能,只是加工的数据略有不同的指令组 (程序段 )需要在一个程序的若干不同地方使用多次,或是在一个程序中的多个地方或多个程序中的多个地方用到了同一段程序。这些程序段的功能和结构形式都相同,只是某些变量的赋值不同。那么就可以将这段程序抽取出来存放在某一存储区域,每当需要执行这段程序时,就转到这段程序去执行,执行完后再返回到原来的程序继续运行。把抽取出来的这段具有特定功能的程序段称为子程序。
第 6章 子 程 序 设 计例如,计算某个数的立方根可能在一个程序中使用多次,但每次自变量不同。又如,设计名字识别程序时,有时需要判断字符是否为字母,有时又需要判断字符是否为分隔符。这样的指令组所包含的指令,少则几条十几条,多则数十条几百条。如果每一处都重复把它写一次,这显然太浪费程序设计的时间和计算机的存储空间。但这又不能设计成循环程序。因此,人们通过实践,
在程序设计的早期就想出了一种较好的办法,就是把这组指令分离出来,单独写成一个所谓的,子程序,,并建立进入它和从它出来时所需要的连接信息。只需在需要处调用这个子程序就行。
换言之,子程序方法使得人们把,多次编写,的情况转变成,一次编写,多次调用,的情况。子程序相当于高级语言中的过程或函数。
第 6章 子 程 序 设 计图 6-1 主程序与子程序的关系通常,把要调用程序的那个程序称为主程序或调用程序,也称转子;而把被调用的程序称为子程序或被调用程序,也称返主。
把它们之间控制的转移称为子程序的连接。主程序与子程序的关系如图 6-1所示。
转子 子程序主程序返主第 6章 子 程 序 设 计在程序设计的实际应用中,子程序的引入可以节省存储空间及程序设计所花费的时间,有利于设计一个大而复杂的程序,
即把一个大而复杂的程序设计成一个主程序和若干个子程序。
这有助于减少程序的复杂性,便于模块化设计,也便于程序的调试及修改等。但子程序也有其不足之处,这就是要多花费一些机器时间。
第 6章 子 程 序 设 计
6.1.2 子程序的分类子程序的种类很多,有单入口、单出口子程序,也有多入口、多出口子程序。对于一些复杂的子程序,还常常要调用别的子程序。这种调用可以是一个子程序调用另外的一个子程序,
也可以是一个子程序调用自身,这就形成了嵌套结构和递归结构,分别称为嵌套子程序和递归子程序。子程序嵌套的示意图如图 6-2所示。
第 6章 子 程 序 设 计图 6-2 嵌套子程序示意图子程序
1
主程序子程序
2
第 6章 子 程 序 设 计
6.1.3 子程序的特性
1.通用性子程序具有通用性,便于共享。例如,键盘管理程序,磁盘读写程序,标准函数程序等等,许多程序中要用到这些程序,
这种可共享的程序最适宜写成子程序。而只能完成特定功能的子程序,由于缺乏通用性,难于共享,就不适宜写成子程序。
第 6章 子 程 序 设 计
2.重复性子程序是可多次重复使用的。 — 个子程序只占一段存储空间,但可以多次地调用它,这样就避免了编程人员的重复劳动,
节省了存储空间。由于增加了调用、返回指令以及现场保护,
因此程序执行时间会增长。如果一个程序段只用到一次,就没有必要编写成子程序形式。
第 6章 子 程 序 设 计
3.可重定位性可重定位性是指子程序可以存放在存储区的任何地址处。
如果子程序只能存放在固定的地址处,则在编写主程序时要特别注意存储单元的分配,不要使主程序占用了子程序的存储单元而破坏子程序,这样就会给编程人员带来很大麻烦,而且在装配主程序和子程序时往往造成存储空间的冲突或浪费。为了使子程序可重定位在内存的任意区域中,编制子程序时,不应采用绝对地址,而应全部使用相对地址。
第 6章 子 程 序 设 计
4.可递归性前面已提及递归程序的概念。在图 6-2中,当子程序 1和子程序 2是同一个程序时,这种调用就是递归调用。为使子程序具有可递归性,应当利用堆栈和寄存器作为中间结果的暂存器,
而不能用固定的存储单元做暂存器。
第 6章 子 程 序 设 计
5.可重入性可重入性是指子程序可被中断并能再次被中断程序调用。
具体地讲就是,如果子程序可被中断,在中断处理中又被中断服务程序调用,并且能为中断服务程序和中断了的子程序提供正确的结果,这种子程序就是可重入的。同样,为使子程序具有可重入性,也应当利用堆栈和寄存器作为中间结果的暂存器,
而不能用固定的存储单元做暂存器。
第 6章 子 程 序 设 计
6.2 子程序的结构形式
6.2.1 子程序的定义子程序必须经过定义以后才能被调用。其定义格式如下:
过程名 PROC 类型属性;过程体过程名 ENDP
功能:实现子程序的定义。
…
第 6章 子 程 序 设 计说明:
(1) PROC和 ENDP是过程的定义符和结束符,它们必须成对出现,它们前面的过程名必须一致,过程名为一标识符,它的写法和标号的写法相同,它实际上是子程序入口的符号地址。
(2) 类型属性可以是 NEAR或 FAR两种类型,缺省时为 NEAR属性。过程属性的确定方法是当调用过程 (主程序 )和被调用过程
(子程序 )在同一个代码段中则用 NEAR属性;当调用过程和被调用过程不在同一个代码中则使用 FAR属性。
第 6章 子 程 序 设 计
(3) 在子程序设计中,除了子程序必须经过定义以外,还必须解决主程序和子程序的链接、主程序和子程序的参数传递这两个问题。主程序和子程序的链接是通过执行调用指令和返回指令实现的,而主程序和子程序的参数传递可有多种方式,
这将在 6.5节中讨论。先讨论第一个问题。
1) 调用程序和子程序在同一个代码段的程序结构若调用程序和子程序在同一个代码段内,其程序框架如例 6-1
所示。此程序给出了代码段中含有主程序和一个子程序的情况,
实际上可以含有多个子程序,子程序可以是 NEAR型或缺省。注意段长不能超过 64 KB。
第 6章 子 程 序 设 计例 6-1
CODE SEGMENT
MAIN PROC FAR
PUSH DS
MOV AX,0
PUSH AX;其他代码
CALL SUBA
RET
…
…
第 6章 子 程 序 设 计
MAIN ENDP
SUBA PROC
RET
SUBA ENDP
CODE ENDS
END MAIN
…
例 6-1的子程序 SUBA与主程序 MAIN同处在一个代码段 CODE之中。
第 6章 子 程 序 设 计
2) 调用程序和子程序在不同代码段的程序结构若调用程序和子程序在不同的代码段中,其程序框架如例
6-2所示。其中的子程序为 FAR属性,CALL指令显式说明其 FAR
属性。此程序只给出了一个模块内的多个代码段的情况,也可将程序设计为多模块的情况。
第 6章 子 程 序 设 计例 6-2
CODEA SEGMENT
MAIN PROC FAR
PUSH DS
MOV AX,0
PUSH AX;其他代码
CALL FAR PTR SUBB
RET
MAIN ENDP
CODEA ENDS
…
…
第 6章 子 程 序 设 计
CODEB SEGMENT
SUBA PROC FAR
CALL FAR PTR SUBB
RET
SUBA ENDP
SUBB PROC FAR
RET
SUBB ENDP
CODEB ENDS
END MAIN
…
…
…
第 6章 子 程 序 设 计由于 SUBB既被段间调用又被段内调用,所以必须是 FAR属性。
例 6-2给出的子程序 SUBA,SUBB与主程序 MAIN不在同一代码段中,主程序 MAIN在 CODEA代码段中,而子程序 SUBA,SUBB在
CODEB代码段中。
例 6-1和例 6-2说明,汇编语言程序中的主、子程序,既可以在同一个代码段中 (如例 6-1),也可以在不同的代码段中 (如例 6-2)。
第 6章 子 程 序 设 计
6.2.2 子程序调用方法说明一个完整的子程序,应当包括子程序调用方法说明、保护现场和恢复现场、子程序定义等部分。为了使用的方便,子程序应以文件形式编写。子程序文件由子程序说明和子程序本身构成。
子程序说明部分要求语言简明、确切。
子程序说明一般由如下几部分组成:
(1) 功能描述:包括子程序的名称、功能、性能指标 (如执行时间 )等。
(2) 所用的寄存器和存储单元。
(3) 子程序的入口、出口参数。
(4) 子程序中又调用的其他子程序。
(5) 调用实例。
第 6章 子 程 序 设 计例如,有一子程序说明如下:; 子程序 DTOB; 将两位十进制数 (BCD码 )转换成二进制数; 入口参数,AL寄存器中存放十进制数; 出口参数,CL寄存器中存放转换完的二进制数; 所用寄存器,BX; 执行时间,0.06 ms
第 6章 子 程 序 设 计看了这一子程序说明,尽管还不知道子程序本身的情况,
但根据说明已可以调用这个子程序了。该子程序说明的第一、
二行告诉我们 DTOB子程序完成的功能。入口参数说明这一程序在调用前应将要转换的十进制数送入 AL寄存器。出口参数说明,
子程序执行完后,转换结果就在 CL寄存器中,所用寄存器是说该子程序执行过程中要用到 BX寄存器,因此在调用本子程序前,
若 BX寄存器里存放着有用数据的话,应该事先转存或保护起来,
否则可能会被破坏。执行时间则说明了该程序执行所需的时间是 0.06 ms。有的说明中还给出一个调用实例,以实例的形式教给用户如何使用和调用子程序。
第 6章 子 程 序 设 计
6.3 子程序调用和返回指令
6.3.1 调用指令
1.段内直接调用格式,CALL DST
CALL NEAR PTR DST
功能:将子程序的返回地址 (断点地址 )存入栈中,并转移到子程序入口地址去执行子程序。
执行的操作,(SP)-2→SP
(IP)→(SP)+1,(SP)
(IP)+ D16→IP
第 6章 子 程 序 设 计该指令先把返回地址 (即主程序中 CALL指令的下一条指令的地址 )保存到堆栈中,以便子程序执行完返回主程序时使用,
然后则转移到子程序的入口地址去执行子程序。指令中的 DST
一般是过程名 (被调用过程 ),即子程序入口的符号地址,D16是子程序入口地址和 CALL指令之间的偏移量。
第 6章 子 程 序 设 计例 6-3
CODE SEGMENT
MAIN PROC FAR
CALL SUBR1
MAIN ENDP
SUBR1 PROC NEAR
SUBR1 ENDP
CODE ENDS
…
…
…
第 6章 子 程 序 设 计图 6-3 段内直接调用指令 CALL执行示意图
FF
00
01
…
…
…
0 6 0 3 H
0 5 0 3 H
0 5 0 2 H
0 5 0 1 H
0 5 0 0 H
代码段
C A L L S U B R 1
(I P )
S U B R 1
03
05
堆栈
(I P )
(b ) (c )
堆栈
(S P )
(a )
第 6章 子 程 序 设 计本例中执行 CALL SUBR1时,自动将返回地址 (IP)= 0503H,
入栈保存,然后执行 (IP)+D16= 0503H+0100H= 0603H→IP,转到子程序 SUBR1去执行。
图 6-3中,(a)图是 CALL SUBR1执行前堆栈; (b)图是 CALL
SUBR1执行后堆栈; (c)图是 CALL SUBR1机器指令格式示意图。
第 6章 子 程 序 设 计
2.段内间接调用格式,CALL DST
CALL WORD FAR DST
其中,DST为通用寄存器或字存储器。
功能:将子程序的返回地址 (断点地址 )存入栈中,并转移到子程序的入口地址执行子程序。
执行的操作,(SP)-2→SP
(IP) →(SP)+1,(SP)
(EA) →IP
其中,EA是由 DST的寻址方式所确定的有效地址。
第 6章 子 程 序 设 计例 6-4
CALL DX ;子程序的入口地址存于寄存器 DX中
CALL WORD PTR[SI] ;子程序的入口地址在存储单元中第 6章 子 程 序 设 计
3.段间直接调用格式,CALL FAR PTR DST
其中,DST为子程序名。
功能:将子程序的返回地址 (断点地址 )存入栈中,并转移到子程序的入口地址执行子程序,子程序入口地址的偏移地址送 IP,子程序入口地址的段地址送 CS。
第 6章 子 程 序 设 计执行的操作:
(SP)-2→SP
(CS) →(SP)+1,(SP)
(SP)-2→SP
(IP) →(SP)+1,(SP)
偏移地址 → IP
段地址 → CS
第 6章 子 程 序 设 计段间直接调用指令同样是先保存返回地址,然后转移到由
DST指定的地址去执行。和段内调用指令所不同的是,此时的主程序和子程序不在同一个代码段,因此返回地址的保存及转向地址的设置都必须把段地址考虑在内。指令中的 DST可以是子程序入口的符号地址,而在机器语言格式中指定的子程序入口地址的偏移地址就在指令的第 2,3个字节中,子程序入口地址的段地址就在指令的第 4,5个字节中,即子程序入口的偏移地址和段地址在操作码的后面,是指令的一部分。
第 6章 子 程 序 设 计例 6-5 显示一个字符 M,并输出回车和换行,要求采用段间直接调用的方式。
STACK SEGMENT PARA STACK 'STACK'
DB 100 DUP(?)
STACK ENDS
CODEA SEGMENT
ASSUME CS∶CODEA,SS∶STACK
START,CALL FAR PTR DISP
MOV AH,4CH
INT 21H
CODEA ENDS
第 6章 子 程 序 设 计
CODEB SEGMENT
ASSUME CS∶CODEB
DISP PROC FAR
MOV DL,'M'
MOV AH,2
INT 21H
MOV DL,0DH ;输出回车符
MOV AH,2
INT 21H
MOV DL,0AH ;输出换行符
MOV AH,2
INT 21H
RET
DISP ENDP
CODEB ENDS
END START
第 6章 子 程 序 设 计
4.段间间接调用格式,CALL DWORD PTR DST
其中,DST是双字存储器操作数。
功能:将子程序的返回地址 (断点地址 )存入栈中,并转移到子程序的入口地址执行子程序,由 DST的寻址方式确定的有效地址 EA送 IP,EA+2送 CS。
第 6章 子 程 序 设 计执行的操作,(SP)- 2→SP
(CS)→(SP)+1,(SP)
(SP)- 2→SP
(IP)→(SP)+1,(SP)
(EA)→IP
(EA+2)→CS
例 6-6 CALL DWORD PTR[BX]
CALL DWORD PTR ADDR
第 6章 子 程 序 设 计
6.3.2 返回指令返回指令 RET通常作为子程序的最后一条指令,它使子程序在执行完以后能返回主程序去继续执行。由于主程序在执行
CALL指令调用子程序时,将返回地址已保存在堆栈中,所以执行 RET指令时,只须将返回地址弹出栈顶并送 IP。若是段间返回,还须将段地址弹出栈顶并送 CS。
第 6章 子 程 序 设 计
1.段内返回指令格式,RET
功能:根据存放于栈中的断点地址返回主程序。
执行的操作,((SP)+1,(SP)) →IP
(SP)+2→SP
第 6章 子 程 序 设 计
2.段内带立即数返回指令格式,RET n
功能:根据存放于栈中的断点地址返回主程序。
执行的操作,((SP)+1,(SP)) →IP
(SP)+2→SP
(SP)+n→SP
第 6章 子 程 序 设 计其中,n为一表达式,根据它计算出的值获取相应的位移量。
它的作用是允许返回地址弹栈后修改栈顶指针。调用程序在执行 CALL指令调用子程序之前把子程序所需要的参数入栈,以便子程序运行时使用这些参数。当子程序执行完返回调用程序后,
这些参数已不再需要,利用 n修改栈顶指针使 SP指向参数入栈以前的位置。
第 6章 子 程 序 设 计
3.段间返回指令格式,RET
功能:根据存放于栈中的断点地址返回主程序。
执行的操作,((SP)+1,(SP)) →IP
(SP)+2→SP
((SP)+1,(SP)) →CS
(SP)+2→SP
第 6章 子 程 序 设 计
4.段间带立即数返回指令格式,RET n
功能:根据存放于栈中的断点地址返回主程序。
执行的操作,((SP)+1,(SP)) →IP
(SP)+2→SP
((SP)+1,(SP)) →CS
(SP)+2→SP
(SP)+ n→SP
这里 n的含义与段内带立即数返回指令中的含义相同。调用指令
CALL和返回指令 RET都不影响条件码。
第 6章 子 程 序 设 计需要说明的是,CALL和 RET指令都有类型属性的问题,即段内调用和返回为 NEAR属性,段间调用和返回为 FAR属性。 80x86
的汇编程序用过程定义 PROC的类型属性来确定 CALL和 RET指令的属性。也就是说,如果所定义的过程是 FAR属性,那么对它的调用和返回一定都是 FAR属性;如果所定义的过程是 NEAR属性,那么对它的调用和返回也一定是 NEAR属性。这样,用户只需要在定义过程时考虑它的属性,而 CALL和 RET指令的属性就可以由汇编程序来确定了。
第 6章 子 程 序 设 计
6.4 子程序的设计从主程序与子程序之间的调用关系来看,它们有如下一些特点:对子程序而言,要被多次使用,每次都应从主程序得到不同的初始数据;而且要把加工后的结果告诉主程序;要能正确地返回到调用的地方 (调用地点不同,因而返回处也随之不同 )。对主程序而言,在多次调用子程序时,需要把不同的调用点告诉子程序;要提供供子程序加工的数据 (因调用点不同,所提供的数据一般也各异 );还要把结果从子程序中取回,以供使用。
第 6章 子 程 序 设 计根据以上特点,它们各自的工作步骤一般是:
主程序:
(1) 在约定位置给出子程序处理所需的信息;
(2) 转向子程序的入口,即将控制转向子程序,同时,要给出返回地址信息;
(3) 当由子程序返回主程序时,主程序从约定的位置取出处理结果。
第 6章 子 程 序 设 计子程序:
(1) 保留返回点的信息,保存子程序要使用的通用寄存器,
以免其内容被破坏;
(2) 取出要加工的初始数据进行处理,并向约定位置送处理后的结果;
(3) 按返回点信息返回主程序,即将控制转到主程序。
在这里,需要对子程序设计中的几个问题:主程序与子程序的连接、所用寄存器及工作单元内容的保护、参数的传递、
现场保护和恢复等作一讨论。其中,对参数的传递方法的讨论在 6.5节中进行。
第 6章 子 程 序 设 计
1.主程序与子程序的连接例 6-7 如下程序段:
CODE SEGMENT
STRT:
MOV AL,XX ;设要求转换的数在 XX单元
PUSH BX
CALL DTOB
NEXT,MOV YY,CL ;转换结果存放在 YY单元
POP BX
DTOB PROC
RET
DTOB ENDP
CODE ENDS
END STRT
…
…
…
第 6章 子 程 序 设 计主程序向子程序的转移是由 CALL指令完成的。这个主程序中的 MOV AL,XX指令是传送入口参数的,PUSH BX指令保护 BX寄存器的内容。 CALL指令的执行分两步进行:
(1) 先将 CALL指令下面一条指令的地址 NEXT,即断点入栈,
也称保护断点。
(2) 程序转到 DTOB去执行,实现转子。
子程序执行到最后一条指令 RET时,将栈的内容 (断点 NEXT)
弹出到指令指针中,使程序从 NEXT处指令继续执行,实现了从子程序返回。 POP BX将恢复 BX的内容。
第 6章 子 程 序 设 计
2.寄存器及所用工作单元内容的保护如果在子程序中要用到某些寄存器或存储单元,为了不破坏原有信息,要将它们的内容入栈加以保护,存入另外一些空闲的存储单元或某些目前不用的寄存器中。
保护可以在子程序中实现,也可以在主程序中实现。一般情况下,在子程序的开始部分安排一段保护程序,用以对它所用到的寄存器或存储单元内容加以保护。在子程序结束前,再将有关的内容恢复。例如:
第 6章 子 程 序 设 计
DTOB PROC
PUSH BX
POP BX
RET
DTOB ENDP
…
第 6章 子 程 序 设 计在子程序中实现保护比较好,遇到粗心的用户忘记在调用前安排保护的话,有关内容也不会破坏。如果在子程序中没有安排保护指令,则可以在主程序中,在调用前安排保护指令,调用后安排恢复指令。用于为中断服务的子程序,则一定要在子程序中安排保护指令。因为中断是随机出现的,也就是说,主程序中断而转入子程序的地点是不固定的,无法在主程序安排保护程序。
第 6章 子 程 序 设 计
3.参数的传递子程序中允许改变的数据叫参数。参数分为入口参数和出口参数。参数的使用使子程序具有灵活、方便、通用的优点。
入口参数使子程序可对不同数据进行处理,出口参数使子程序可送出不同的结果。一般的子程序都可接收参数。
第 6章 子 程 序 设 计参数传递的方法一般有三种:
(1) 寄存器传递:适合于参数较少的情况。
(2) 参数表传递:适合于参数较多的情况,但要求事先建立一个参数表,参数表可建在内存中或外设端口中。
(3) 栈传送:适合于参数多且子程序有嵌套或递归调用的情况,主程序将参数入栈,子程序将参数从栈中弹出。
无论哪种方法,主程序与子程序的接口要设计好。子程序要求到哪里取参数,主程序就应将参数送到那儿,而且要注意参数的个数、顺序和类型。
第 6章 子 程 序 设 计
4.现场保护和现场恢复现场保护就是在子程序的功能实现前,把将要用到的寄存器中的原有内容保存起来。现场恢复就是子程序的功能实现后,
将数据取出再送回原来的寄存器中,以保证子程序执行前后,这些寄存器的内容不被改变,从而不影响主程序对这些寄存器的使用。
在 80x86中,现场保护是通过入栈指令 PUSH来实现的,而现场恢复是通过弹栈指令 POP来实现的。不论是现场保护还是现场恢复,都需要使用栈。设有寄存器 AX,BX,CX,DX和 EX需要保护,若现场保护 (即入栈的顺序 )是 AX,BX,CX,DX和 EX;由于栈的操作是采用先进后出的方式,因此,现场恢复 (即弹栈的顺序 )恰好与入栈的顺序相反,应是 EX,DX,CX,BX和 AX,程序设计为第 6章 子 程 序 设 计
SUBA PROC
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH EX;子程序中的其他代码
POP EX
POP DX
POP CX
POP BX
POP AX
RET
SUBA ENDP
…
第 6章 子 程 序 设 计
6.4.1 子程序的设计子程序的编写应采用较好的算法,这可使子程序运行速度比较快,又节省内存,真正达到子程序在一个程序中能被多个程序使用或在多处被用到的目的。根据 6.2.2节的叙述,编写子程序时还应提供有关子程序的足够信息,使得使用时不再需要考虑子程序的内部结构,就能方便地调用。
例 6-8 插入字符串。
编程在一个给定的字符串指定的位置上插入另一个字符串,
在这里,给定的字符串称为目的字符串,插入的另一个字符串称为源字符串,如图 6-4所示。
第 6章 子 程 序 设 计图 6-4 插入字符串示意图源字符串 要求插入的位置目的字符串目的字符串执行前执行后第 6章 子 程 序 设 计本题采用子程序的方法设计。
1) 子程序调用方法说明子程序名,STRINSERT。
功能:在一个给定的字符串指定的位置上插入另 — 个字符串。
输入:在进入时,DS∶BX 指向源字符串,ES∶BP 指向目的字符串,ES∶DX 指向要在目的字符串中插入源字符串的位置。
每个字符串都是以一个说明字符串长度的 16位整数开始的。
第 6章 子 程 序 设 计输出:返回时,已在目的字符串指定的位置中插入了源字符串,因此目的字符串长度增加了。
寄存器占用:不用修改寄存器。
段访问:在输入时,数据段必须包含源字符串,附加段必须包含目的字符串。
子程序调用:无。
注释:无。
第 6章 子 程 序 设 计
2) 子程序清单
ESS EQU ES:[SI]
DSD EQU BYTE PTR[DI]
STRINSERT PROC FAR
PUSH SI
PUSH DI
PUSH CX
PUSH AX
MOV SI,BP
ADD SI,ES:[SI]
INC SI
MOV DI,SI
第 6章 子 程 序 设 计
MOV AX,[BX]
ADD DI,AX
ADD ES:[BP],AX
MOV CX,SI
SUB CX,DX
INC CX
STD
REP MOVS DSD,ESS
MOV DI,DX
MOV SI,BX
CLD
LODSW
MOV CX,AX
第 6章 子 程 序 设 计
REP MOVSB
STRINSERTEXIT:
POP AX
POP CX
POP DI
POP SI
RET
STRINSERT ENDP
第 6章 子 程 序 设 计例 6-9 从键盘键入一个不超过 65 535的无符号整数,然后把它转换成等值的二进制数,最后再把该数以十六进制的形式在显示器上显示出来。用子程序编制程序。
程序的设计可分两步完成:
① 实现十进制数转换成二进制数,即将键入的 0~ 9数字的
ASCII码转换成对应的二进制数值,然后利用,累加和乘以 10加键入值,的算法将一个十进制数转化为等值的二进制数。
② 将转换好后的二进制数再以十六进制的形式在显示器上显示出来。
第 6章 子 程 序 设 计具体实现时,采用子程序的结构。首先用 DECBIN子程序实现从键盘键入十进制数并把它转换为二进制数 BX,然后用
BINHEX子程序把 BX中的二进制数以十六进制的形式显示输出。
为避免键入的十进制数和显示的十六进制数重叠,用子程序
CRLF实现回车换行的功能。
第 6章 子 程 序 设 计
1) 子程序调用方法说明子程序名,DECBIN。
功能:将键盘键入的十进制数转换为二进制数。
输入:从键盘键入十进制数。
输出,BX。
子程序调用:无。
子程序名,CRLF。
功能:回车、换行。
输入:无。
第 6章 子 程 序 设 计输出:在显示终端显示回车换行。
子程序调用:无。
子程序名,BINHEX。
功能:将 BX中的二进制数以十六进制的形式显示。
输入,BX。
输出:在显示终端显示十六进制数。
子程序调用:无。
第 6章 子 程 序 设 计
2) 子程序清单
DECBIN PROC NEAR
MOV BX,0
NEWCHAR,MOV AH,01
INT 21H
SUB AL,30H
JB EXIT
CMP AL,09
JA EXIT
CBW
XCHG AX,BX
MOV CX,10D
MUL CX
XCHG AX,BX
ADD BX,AX
JMP NEWCHAR
第 6章 子 程 序 设 计
EXIT,RET
DECBIN ENDP
CRLF PROC NEAR
MOV DL,0DH
MOV AH,02
INT 21H
MOV DL,0AH
MOV AH,02
INT 21H
RET
CRLF ENDP
BINHEX PROC NEAR
MOV CH,4
第 6章 子 程 序 设 计
AGAIN,MOV CL,4
ROL BX,CL
MOV AL,BL
AND AL,0FH
ADD AL,30H
CMP AL,3AH
JB DISPLAY
ADD AL,07H
DISPLAY,MOV DL,AL
MOV AH,02
INT 21H
DEC CH
JNZ AGAIN
RET
BINHEX ENDP
第 6章 子 程 序 设 计
6.4.2 子程序的调用例 6-10 编写十进制数转换成二进制数的程序。实现时采用调用例 6-9中的 DECBIN子程序,BINHEX和 CRLF子程序的方法。
CODE SEGMENT
MAIN PROC FAR
ASSUME CS∶CODE
START,PUSH DS
SUB AX,AX
PUSH AX
CALL DECBIN
第 6章 子 程 序 设 计
CALL CRLF
CALL BINHEX
RET
MAIN ENDP
CODE ENDS
END START
第 6章 子 程 序 设 计
6.5 子程序的参数传递方法
6.5.1 通过寄存器传递参数在主程序中,调用子程序前,将参数保存在某些通用寄存器中,子程序就可直接使用寄存器中的入口参数,80386/80486
的每个通用寄存器都可用于保存参数。同样,出口参数也可通过寄存器返回给主程序。用这种方法传递参数简单快捷,但需要占用通用寄存器。而寄存器数量十分有限,当要传递的参数较多时,采用寄存器传递就不行了,因此这种方法只适合于参数较少的情况。
第 6章 子 程 序 设 计例 6-11 编程实现将从键盘上输入的小写字母转换成大写字母后输出。
分析:由 ASCII码编码表可知,英文大、小写的 26个字母字符的编码值是顺序递增的,且各小写字母与相应大写字母之间的编码差值均为 32,因此当要将一个小写字母转换为大写字母时,只要将其 ASCII码值减去 32即可。为简化程序,将判断输入的字符是否为小写字母的工作编为一子程序,该子程序将判断的结果通过标志寄存器中的 CF标志返回给主程序,CF= 0表示是小写字母,CF= 1表示不是小写字母。主程序通过 AL寄存器将要判断的内容传递给子程序。
第 6章 子 程 序 设 计根据上述分析,编写的程序如下:
DATA SEGMENT
MSG DB 'ERROR! ',0DH,0AH,'$' ; 0DH为回车符,0AH换行符,$为字符串;结束符
DATA ENDS
CODE SEGMENT 'CODE'
ASSUME CS∶CODE,DS∶DATA
START PUSH CS
POP DS
INPOT,MOV AH,01H
INT 21H ;读入一字符,送到 AL,通过 AL传递参数给子程序
CALL COMPARE ;调用子程序 COMPARE
第 6章 子 程 序 设 计
JC ERR ;子程序通过 CF传递结果给主程序。 CF= 1,转 ERR
SUB AL,32 ; CF= 0,将小写转换为大写
MOV DL,AL
MOV AH,02
INT 21H ;输出转换后的大写字母
MOV AH,4CH
INT 21H ;返回 DOS
ERR:
MOV DX,OFFSET MSG
MOV AH,09
INT 21H ;输出出错信息,ERROR!,
JMP INPUT ;跳转到 INPUT,循环输入判断第 6章 子 程 序 设 计
COMPARE,;子程序
CMP AL,'a'
JB SETFLAG
CMP AL,'z'
JA SETFLAG
CLC
RET
SETFLAG:
STC
RET
CODE ENDS
END START
第 6章 子 程 序 设 计
6.5.2 用存储单元传递参数主程序与子程序之间可利用指定的存储单元传递参数,这样可使数据便于保存,而且适于参数较多的情况。采用这种方法时,要事先在内存中建立一个参数表,通过传送参数地址的方法实现调用程序和子程序间的参数传递。具体方法是先建立一个地址表,该表由参数地址构成,然后把表的首地址通过寄存器或堆栈传递给子程序。
第 6章 子 程 序 设 计例 6-12 用变址寻址的方法实现多精度加法运算。
STACK SEGMENT PARA STACK
DB 64 DUP('MYSTACK')
STACK ENDS
MYDATA SEGMENT PARA 'DATA'
TABLE DW 1234H,5678H,9ABCH,0DEF0H,111H,222H,333H;建立加数参数表
DW 4444H,5555H,6666H,7777H,8888H,9999H,0AAAAH
DW 0BBBBH,0CCCCH,0DDDDH,0EEEEH,0FFFFH
LSBANS DW?
MSBANS DW?
MYDATA ENDS
MYCODE SEGMENT PARA 'CODE' ;给宏汇编程序定义代码段第 6章 子 程 序 设 计
MYPROC PROC FAR ;过程被命名为 MYPROC(远过程 )
ASSUME CS∶MUCODE,DS∶MYDATA,SS∶STACK
PUSH DS ;保存 DS寄存器内容
SUB AX,AX ;给 AX寄存器清零
PUSH AX ;把零存到堆栈内
MOV AX,MYDATA ;把存储单元内容传送给 AX
MOV DS,AX ;把 AX内容放到 DS寄存器; 表中前 10个数的多精度加法,使用的是变址寻址
LEA BX,TABLE ;把表的基址偏移量放入 BX内
MOV SI,00H ;把变址置成零
MOV AX,TABLE[SI] ;得到第一个数
MOV CX,09H ;程序要进行 9次加法第 6章 子 程 序 设 计
AGAIN,ADD SI,02H ;变址值增 2
ADD AX,TABLE[SI] ;把下一个数加到和中
ADC DX,00H ;若有进位,则加到 DX寄存器
LOOP AGAIN ;若 CX不为零,则再加一个数
MOV LSBANS,AX ;把 AX内容传送进 LSBANS
MOV MSBANS,DX ;把 DX内容传送进 MSBANS;多精度加法到此结束
RET ;返回 DOS
MYPROC ENDP ;名为 MYPROC的过程到此结束
MYCODE ENDS ;名为 MYCODE的代码段到此结束
END ;整个程序结束第 6章 子 程 序 设 计
6.5.3 通过堆栈传递参数主程序和子程序可将要传送的信息放在栈中,使用时再从栈中弹出。由于栈具有先进后出、后进先出的特性,故多重调用中各重参数的层次很分明,很适于参数多且子程序有嵌套、递归调用的情况。前两种参数传递方法都不能实现递归调用的信息传送。
此时,主程序将参数入栈,子程序将参数从栈中弹出。由于在主程序中是先将参数入栈,然后才执行 CALL指令去调用相应子程序的,(这时,CALL指令将返回地址存于栈顶位置),因此在子程序中为了不破坏栈顶指针,不能直接用退栈指令 POP使参数弹出,
而经常借用 (E)BP寄存器来达到目的。
第 6章 子 程 序 设 计即将某一时刻的栈顶指针 (E)SP的值送给 (E)BP,使 (E)BP指向栈中某一位置,然后用地址表达式 [BP+ disp]间接访问栈的非栈顶字 (或字节 )单元内容,其中,disp是字 (或字节 )单元距离 (E)BP指向位置的相对位移量 (字节数 )。而子程序也可将返回参数存放在栈中由主程序预留的栈空间内,以便返回后主程序能从栈中弹出返回参数。
第 6章 子 程 序 设 计例 6-13 实现两个数组的分别求和。两个数组位于数据段中,将求和程序定义为过程,且主程序和过程分别安排在两个不同的代码段中 (即过程是 FAR类型的 )。
利用堆栈实现主程序向过程的参数传递,要特别注意配合好子程序中参数的读取和返回。
第 6章 子 程 序 设 计程序描述如下:
STACK SEGMENT PARA STACK 'STACK'
SPAE DW 20 DUP(? )
TOP EQU LENGTH SPAE
STACK ENDS
DATA SEGMENT
ARY1 DB 10 DUP(? ) ;定义数组 1
SUM1 DW?
ARY2 DB 100 DUP(? ) ;定义数组 2
SUM2 DW?
DATA ENDS
MAIN SEGMENT ;主程序段
ASSUME CS:MAIN,DS:DATA,SS:STACK
第 6章 子 程 序 设 计
CODE SEGMENT
MAIN PROC FAR
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 ; SUM过程的入口参数 2入栈
CALL SUM
MOV AX,SIZE ARY2
第 6章 子 程 序 设 计
PUSH AX
MOV AX,ARY2
PUSH AX
CALL SUM
RET
MAIN ENDP
CODE ENDS
PROCE SEGMENT ;过程段
ASSUME CS:PROCE,DS:DATA,SS:STACK
SUM PROC FAR
PUSH AX
PUSH BX
第 6章 子 程 序 设 计
PUSH CX
PUSH BP
MOV BP,SP
PUSHF
MOV CX,[BP+ 14] ;数组长度 SIZE由栈 → CX
MOV BX,[BP+ 12] ;数组存放的地址偏移量由栈 → BX
MOV AX,0
ADN,ADD AL,[BX]
ADC AH,0
INC BX
LOOP ADN
MOV [BX],AX ;数组之和送到结果区第 6章 子 程 序 设 计
POPF ;恢复现场
POP BP
POP CX
POP BX
POP AX
RET4 ;返回主程序,并废除参数 1和参数 2
SUM ENDP
PROCE ENDS
END MAIN
第 6章 子 程 序 设 计图 6-5 栈变化示意图参数 1
参数 2
CA L L
之 前
(S P)
参数 1
参数 2
CA L L
之 后
(S P)
CS
IP
参数 1
参数 2
现场保护之 后
(S P)
CS
IP
AX
BX
CX
BP
FL A G
参数 1
参数 2
现场恢复之 后
(S P)
CS
IP
RE T 4
以 后
(S P)
第 6章 子 程 序 设 计对于用栈传递参数,还要说明几点:
(1) 如果主程序没有为返回参数在栈中预留空间,则不能将它们压入栈,这时可采用寄存器或存储器将它们传送给主程序。
(2) 在 16位寻址方式中,近调用时,CALL指令只将返回地址的偏移量 (2个字节 )压入栈,而远调用时则是将返回的完整地址
(4个字节,段址及偏移地址各 2字节 ) 压入栈。在 32位寻址方式中,近调用向栈中压入 4个字节的返回偏移地址,远调用则压入 6
个字节 (2字节的段址和 4字节的偏移地址 )。
(3) 在使 (E)BP指向入口参数之前,一定要保护 (E)BP的原有值。
(4) 若调用程序和子程序在同一个模块 (源程序 )中,子程序可以直接访问模块中的变量。
第 6章 子 程 序 设 计例 6-14 实现数组求和功能。要求数组求和 (不考虑溢出情况 )由子程序实现,其数组元素及结果均为字符型数据。
STACKSG SEGMENT STACK 'STK'
DW 32 DUP('S')
STACKSG ENDS
DATA SEGMENT
ARY DW 1,2,3,4,5,6,7,8,9,10 ;定义数组
COUNT DW ($-ARY)/ 2 ;求数组元素个数
SUM DW? ;数组和的地址
DATA ENDS
CODE1 SEGMENT
第 6章 子 程 序 设 计
MAIN PROC FAR
ASSUME CS∶CODE1,DS∶DATA
PUSH DS
XOR AX,AX
PUSH AX
MOV AX,DATA
MOV DS,AX
CALL FAR PTRA ARY_SUM ;调用数组求和子程序
RET
MAIN ENDP
CODE1 ENDS
第 6章 子 程 序 设 计
CODE2 SEGMENT
ASSUME CS∶CODE2
ARY_SUMPROC FAR ;数组求和子程序
PUSH AX ;保存寄存器
PUSH CX
PUSH SI
LEA SI,ARY ;取数组起始地址
MOV CX,COUNT ;取元素个数
XOR AX,AX ;清 0累加器第 6章 子 程 序 设 计
NEXT,ADD AX,[SI] ;累加和
ADD SI,TYPE ARY ;修改地址指针
LOOP NEXT
MOV SUM,AX ;存和
POP SI ;恢复寄存器
POP CX
POP AX
RET
ARY_SUM ENDP
CODE2 ENDS
END MAIN
第 6章 子 程 序 设 计
6.6 子程序的嵌套与递归
6.6.1 子程序的嵌套调用例 6-15 编制子程序,将二进制数转换为十进制数显示输出。
分析:在进行算术运算时,常常要将二进制的结果转换成十进制的形式显示或打印输出。设将 BX寄存器中的二进制数转换为十进制数显示输出。
要将 BX寄存器中的二进制数转换为十进制数显示输出,应计算出 BX中的二进制数里含有多少个 10000D(即 2710H),多少个
1000D(即 03E8H),多少个 100D(即 0064H),多少个 10D(即 000AH),
多少个 1,再把这些数拼上 30H使之变成对应的 ASCII码,再一位一位地显示。
第 6章 子 程 序 设 计
1.子程序调用方法说明子程序名,BINADECI
功 能:将 16位无符号二进制数转换成十进制数显示输出,原数保持不变入口参数,BX中存放待转换的二进制数出口参数:显示输出所用寄存器,CX,DL寄存器调用形式:调用本子程序之前,将待转换的无符号二进制数送至 BX寄存器第 6章 子 程 序 设 计
2.子程序清单
CODE_SEG SEGMENT
BINADECI PROC FAR
ASSUME CS∶CODE_SEG
PUSH CX ;保护现场
PUSH DX
PUSH BX
MOV CX,2710H
CALL DISPDECI
MOV CX,03E8H
CALL DISPDECI
MOV CX,0064H
CALL DISPDECI
第 6章 子 程 序 设 计
MOV CX,000AH
CALL DISPDECI
MOV CX,0001H
CALL DISPDECI
POP BX ;恢复现场
POP DX
POP CX
RET
BINADECI ENDP
DISPDECI PROC NEAR
MOV DL,0
AGAIN,CMP BX,CX
第 6章 子 程 序 设 计
JB EXIT
INC DL
SUB BX,CX
JMP AGAIN
EXIT,ADD DL,30H
MOV AH,02
INT 21H
RET
DISPDECI ENDP
CODE_SEG ENDS
第 6章 子 程 序 设 计这是一个嵌套子程序,即 BINADECI又调用子程序 DISPDECI。在子程序 DISPDECI中没有采用除法的方法,而采用的是减法的方法。
再编制一个主程序,调用并执行这个子程序。主程序描述如下:
CODE SEGMENT
MAIN PROC FAR
ASSUME CS∶CODE
START,PUSH DS
XOR AX,AX
PUSH AX
CALL CRLF
第 6章 子 程 序 设 计
MOV BX,0000011111010011B
CALL BINADECI
CALL CRLF
RET
MAIN ENDP
CRLF PROC NEAR
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
CRLF ENDP
CODE ENDS
END START
第 6章 子 程 序 设 计
6.6.2 子程序的递归调用例 6-16 编制计算 N!(N≥0) 的程序。
阶乘函数递归定义如下:
1 当 N= 0时
N!=
N× (N-1)! 当 N>0时分析:可用递归定义来设计该程序。
第 6章 子 程 序 设 计计算 N!本身是一个子程序,由于 N!= N× (N-1)!,所以为了计算 (N-1)!又递归调用 N!的子程序,只不过每次调用时使用的参数不同而已。递归子程序的设计必须保证每次调用都不破坏以前调用时所用的参数和中间结果;为保证每次调用都能正确无误,一般把每次调用的参数、有关寄存器的内容以及中间结果入栈保存。递归子程序中还必须包括基数的设置,当调用参数达到基数时必须有一条条件转移指令实现嵌套退出,保证能按反向次序退出并计算 N!后返回主程序。
第 6章 子 程 序 设 计
DATA SEGMENT
N DW?
RESULT DW?
DATA ENDS
STACK SEGMENT
DW 100 DUP(0)
TOP LABEL WORD
STACK ENDS
CODE SEGMENT
MAIN PROC FAR
ASSUME CS∶CODE,DS∶DATA,SS∶STACK
第 6章 子 程 序 设 计
START,MOV AX,STACK
MOV SS,AX
MOV SP,OFFSET TOP
PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV AX,N
PUSH AX
CALL FACT
ADDRL,POP RESULT
RET
第 6章 子 程 序 设 计
MAIN ENDP
FACT PROC NEAR
PUSH BP
MOV BP,SP
MOV AX,[BP+4]
CMP AX,0
JNE FACTL
INC AX
JMP EXIT
第 6章 子 程 序 设 计
FACTL,DEC AX
PUSH AX
CAIL FACT
ADDR2,POP AX
MUL [BP+4]
EXIT,MOV [BP+4],AX
POP BP
RET
FACT ENDP
CODE ENDS
END START
第 6章 子 程 序 设 计在程序执行的过程中,子程序 FACT不断地调用自己,每调用一次就把 (N-1)及有关信息入栈保存,直到 N等于基数时为止。
当 N等于 0时开始返回,程序在不断返回的过程中,计算 (N-
1)× N并保存中间结果直到 N等于给定值为止 (本例中假设 N≤8) 。
当 N= 3时,栈状态如图 6-6所示。
第 6章 子 程 序 设 计
( BP )
( BP )
( BP )
( BP )
原 ( BP )
A D D R2
N = 0
原 ( BP )
A D D R2
N = 1
原 ( BP )
A D D R2
N = 2
原 ( BP )
A D D R2
N = 3
图
6-
6
N=
3
时栈状态示意图第 6章 子 程 序 设 计
6.7 综 合 举 例例 6-17 接收键盘输入的数据,然后将输入的数据在内存中传送到另一区域里,并显示在终端上。
分析:首先将传送的源区定义在内存的低地址处,源区的首地址命名为 AREA1,将目的区定义在内存的高地址处,目的区的首地址命名为 AREA2,这样就可以确定数据是从低地址处向高地址处传送。在传送数据时还需要考虑源区与目的区的重叠情况,即源区与目的区不互相重叠以及源区与目的区相互重叠,
这两种情况下传送的方式是不一样的。
第 6章 子 程 序 设 计程序一:源区与目的区不互相重叠
DATA SEGMENT ;定义数据段
HELP1 DB 'ENTER THE CHAR THAT WANTED TO BE MOVED(NUMBER BELOW 50):$'
HELP2 DB 'AFTER MOVING,THE RESULT IS:$'
AREA1 DB 52 ;定义源区
DB?
DB 52 DUP(?)
AREA2 DB 52 DUP('? ') ;定义目的区
DATA ENDS
CODE SEGMENT ;定义代码段
ASSUME CS:CODE,DS:DATA
第 6章 子 程 序 设 计
START,MOV AX,DATA
MOV DS,AX
MOV DX,OFFSET HELP1 ;提示输入数据
MOV AH,09H
INT 21H
MOV DL,0AH ;输出换行符
MOV AH,02
INT 21H
MOV DL,0DH ;输出回车符
MOV AH,02
INT 21H
MOV DX,OFFSET AREA1 ;将接收的数据放在源区
MOV AH,0AH
INT 21H
第 6章 子 程 序 设 计
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
MOV SI,OFFSET AREA1+1
MOV CL,[SI] ;设置循环次数
XOR CH,CH
MOV SI,OFFSET AREA1+2
MOV DI,OFFSET AREA2
CLD ;由低地址处向高地址处进行传送第 6章 子 程 序 设 计
AGAIN,MOV AL,[SI] ;开始传送
MOV [DI],AL
INC DI
INC SI
DEC CL
JNZ AGAIN ;计数器为 0时传送完毕
MOV AL,'$'
MOV [DI],AL
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
MOV DX,OFFSET HELP2 ;提示要输出数据第 6章 子 程 序 设 计
MOV AH,09H
INT 21H
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
MOV DX,OFFSET AREA2 ;输出传送后的数据
MOV AH,09H
INT 21H
MOV AH,4CH ;返回 DOS
INT 21H
CODE ENDS
END START
第 6章 子 程 序 设 计程序二:源区与目的区相互重叠
DATA SEGMENT ;定义数据段
HELP1 DB 'ENTER THE CHAR THAT WANTED TO BE MOVED(NUMBER BELOW 50):$'
HELP2 DB 'AFTER MOVING,THE RESULT IS:$'
AREA1 DB 50
DB?
DB 50 DUP(?)
AREA2 EQU AREA1+5
N EQU 50
DATA ENDS
STACK SEGMENT PARA STACK 'STACK' ;定义堆栈段
TEMP DB 50 DUP(?)
第 6章 子 程 序 设 计
STACK ENDS
CODE SEGMENT ;定义代码段
ASSUME CS:CODE,DS:DATA
ASSUME ES:DATA,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV DX,OFFSET HELP1 ;提示输入数据
MOV AH,09H
INT 21H
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
第 6章 子 程 序 设 计
MOV AH,02
INT 21H
MOV DX,OFFSET AREA1 ;将接收的数据放在源区
MOV AH,0AH
INT 21H
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
MOV SI,OFFSET AREA1+1 ;确定循环次数
MOV CL,[SI]
XOR CH,CH
第 6章 子 程 序 设 计
MOV SI,OFFSET AREA1+2
MOV DI,OFFSET AREA2
STD ;数据块从后向前传送
ADD SI,CX
ADD DI,CX
MOV AX,'$' ;把字符 '$' 放在最后一个字符后面
MOV [DI],AX
DEC SI ;确定从源区传送的首地址
DEC DI ;确定目的区的首地址
REP MOVSB ;传送数据
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
第 6章 子 程 序 设 计
INT 21H
MOV DX,OFFSET HELP2 ;提示输出数据
MOV AH,09H
INT 21H
MOV DL,0AH
MOV AH,02
INT 21H
MOV DL,0DH
MOV AH,02
INT 21H
MOV DX,OFFSET AREA2 ;输出传送完后的数据
MOV AH,09H
INT 21H
MOV AH,4CH ;返回 DOS
INT 21H
CODE ENDS
END START
第 6章 子 程 序 设 计讨论:
(1) 关于数据从高地址处向低地址处传送的问题。当源区定义在高地址处,目的区定义在低地址处时,传送方法正好与数据从低地址端向高地址处传送的方法相反。当源区与目的区不互相重叠时,按照减量的方向使用循环结构从后向前地将数据传送到目的区;当源区与目的区相互重叠,可以按照增量的方向使用循环结构从前向后地将数据传送到目的区。
(2) 实际操作时,源区与目的区是否重叠的判断方法有两种。
① 当数据从低地址处向高地址处传送时,判断的方法是看源区内数据块的最后一个单元的地址是否比目的区的首地址小,
若是,则源区与目的区不重叠,否则,源区与目的区重叠。具体实现时,只需将循环传送部分的代码换为如下代码段即可。
第 6章 子 程 序 设 计
MOV CX,N
MOV SI,OFFSET AREA1
MOV DI,OFFSET AREA2
CLD
PUSH SI
ADD SI,N-1
CMP SI,DI
POP SI
JL OK
STD
ADD SI,N-1
ADD DI,N-1
OK,REP MOVSB
第 6章 子 程 序 设 计其中,假定源区内的数据为有符号数,且源区的首地址先送寄存器 SI,目的区的首地址送寄存器 DI,N为源区内数据块长度。
② 当数据从高地址处向低地址处传送时,判断的方法是看传送后目的区内数据块最后一个单元的地址是否比源区的首地址小,若是,则源区与目的区不重叠;否则,源区与目的区重叠。具体实现时,只需将循环传送部分的代码换为如下代码段即可。
第 6章 子 程 序 设 计
MOV CX,N
MOV SI,OFFSET AREA1
MOV DI,OFFSET AREA2
CLD
PUSH DI
ADD DI,N-1
CMP DI,SI
POP DI
JG OK
STD
ADD SI,N-1
ADD DI,N-1
OK,REP MOVSB
第 6章 子 程 序 设 计其中,假定源区内的数据为有符号数,且源区的首地址先送寄存器 SI,目的区的首地址送寄存器 DI,N为源区内数据块长度。
例 6-18 将输入的 10个数转换为二进制数进行比较排序,
最后将排序后的结果显示在终端上。
分析:首先将输入的 10个数据循环读入,调用转换子程序
GETD将每一个数转化为二进制数并依次存放在以 RESULT开始的内存单元中,再调用排序的子程序 CAMP将放在数据缓冲区内的数据进行排序,最后调用输出的子程序 PTDN,将二进制数转化为 ASCII码输出。
第 6章 子 程 序 设 计
DATA SEGMENT ;定义数据段
BUF DB 8 ;存放 ASCII码
DB?
DB 8 DUP(?)
CHANGE DB 5 DUP(?) ;存放 BCD码
RESULT DW 10 DUP(?) ;存放二进制数
DISP DB 10 DUP(?) ;存放转换后的 ASCII码
SIGN DB? ;标志位
HELP1 DB 'ENTER THE NUMBER:$ ' ;提示符
HELP2 DB 'THE RESULT IS,$'
DATA ENDS
STACK SEGMENT PARA STACK 'STACK' ;定义堆栈段
DB 100 DUP(?)
第 6章 子 程 序 设 计
STACK ENDS
CODE SEGMENT ;代码段
ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
START PROC FAR
PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV DX,OFFSET HELP1 ;提示输出
MOV AH,09H
INT 21H
MOV BX,OFFSET RESULT
MOV CX,10 ;循环转换输入的 10个数第 6章 子 程 序 设 计
AGAIN,PUSH CX
MOV DL,0AH
MOV AH,2
INT 21H
MOV DL,0DH
MOV AH,2
INT 21H
CALL GETD
MOV [BX],CX ;将转换后的二进制数存放在 RESULT开头的单元中
INC BX
INC BX
POP CX
第 6章 子 程 序 设 计
LOOP AGAIN
CALL CAMP ;将 RESULT单元中的数排序
MOV DL,0AH
MOV AH,2
INT 21H
MOV DL,0DH
MOV AH,2
INT 21H
MOV DX,OFFSET HELP2 ;提示输出
MOV AH,09H
INT 21H
MOV CX,10
MOV SI,OFFSET RESULT
第 6章 子 程 序 设 计
LOP4,PUSH CX
MOV CX,[SI]
CALL PTDN ;输出
INC SI
INC SI
POP CX
LOOP LOP4
RET
START ENDP;输入十进制数,并将其转换成二进制数的子程序;入口参数:键盘输入数据;出口参数,CX
第 6章 子 程 序 设 计
GETD PROC
PUSH AX
PUSH BX
PUSH DX
PUSH SI
PUSH DI
MOV SI,OFFSET BUF+1 ;输入每个数的字符数
MOV DI,OFFSET CHANGE
PUSH DI
MOV DX,OFFSET BUF
MOV AH,10
INT 21H ;接收键盘输入
MOV BL,[SI] ;实际输入的字符个数送 BL
DEC BL
第 6章 子 程 序 设 计;记录符号位
INC SI
MOV AL,0
MOV SIGN,AL ;符号单元送 0
MOV AL,[SI] ;取实际符号位
CMP AL,'+'
JZ NEXT1 ;若实际符号位为负
MOV AL,1 ;则符号单元送 1
MOV SIGN,AL; ASCII码转换成 BCD码
NEXT1,PUSH BX ;保存字符个数
NEXT2,INC SI
MOV AL,[SI]
AND AL,0FH
第 6章 子 程 序 设 计
MOV [DI],AL ; ASCII码转换成 BCD码送到 CHANG开始的单元
INC DI
DEC BL ;字符个数减 1
JNZ NEXT2 ;若不等于 0,继续转换
POP BX; BCD码转换成二进制码
POP DI
MOV CX,0 ;累加和置初值
AG1,PUSH BX
ADD CX,CX
MOV BX,CX
ADD CX,CX
ADD CX,CX
ADD CX,BX ;累加和 *10
第 6章 子 程 序 设 计
MOV BL,[DI]
MOV BH,0
INC DI
ADD CX,BX ;累加和 *10后加下一个 BCD数字
POP BX
DEC BL
JNE AG1 ;未转换完时继续
MOV AL,SIGN
OR AL,AL ;查看符号位
JZ DONE
NEG CX ;若为负数求补
DONE,POP DI
第 6章 子 程 序 设 计
POP SI
POP DX
POP BX
POP AX
RET
GETD ENDP;将数据缓冲区内的数据按照升序的顺序排列的子程序;入口参数:缓冲区内的数据;出口参数:排好后的缓冲区的数据
CAMP PROC
PUSH AX ;保护现场
PUSH BX
PUSH CX
第 6章 子 程 序 设 计
PUSH DX
PUSH SI
MOV DX,9
LOP1,MOV SI,OFFSET RESULT ;外循环
MOV CX,DX
MOV BL,0
LOP2,MOV AX,[SI] ;定义内循环
CMP AX,[SI+2]
JGE LOP3
XCHG AX,[SI+2]
MOV [SI],AX
OR BL,1
第 6章 子 程 序 设 计
LOP3,INC SI
INC SI
DEC CX
JNZ LOP2
AND BL,BL
JZ EXIT ;循环结束
DEC DX
JNZ LOP1
EXIT,POP SI
POP DX
POP CX
POP BX
POP AX ;恢复现场
RET
第 6章 子 程 序 设 计
CAMP ENDP;输出 16位二进制带符号数的子程序;入口参数,CX中为待输出的数据;所用子程序,QUAN
PTDN PROC
PUSH AX
PUSH BX
PUSH DX
PUSH SI ;保护现场
MOV BX,OFFSET DISP ; BX指向缓冲区
MOV AL,20H ;输出的数据间需要有空格符
MOV [BX],AL
INC BX
第 6章 子 程 序 设 计
MOV AL,CH
OR AL,AL ;查看符号位
JNS PLUS ;正数转 PLUS
NEG CX ;负数求补
MOV AL,'-'
MOV [BX],AL ;缓冲区存入负号
JMP GOON
PLUS,MOV AL,'+'
MOV [BX],AL ;缓冲区存入正号
GOON,MOV AL,0
PUSH AX
第 6章 子 程 序 设 计
INC BX
MOV SI,10000
CALL QUAN ;求万位数,并转换为 ASCII码存入缓冲区
MOV SI,1000
CALL QUAN ;求千位数
MOV SI,100
CALL QUAN ;求百位数
MOV SI,10
CALL QUAN ;求十位数
POP AX
MOV AL,30H
ADD AL,CL
MOV [BX],AL ;求个位数
INC BX
MOV AL,'$' ; $送入缓冲区尾,9号功能调用所要求
MOV [BX],AL
第 6章 子 程 序 设 计
INC BX
MOV AL,0AH ;换行以及回车
MOV [BX],AL
INC BX
MOV AL,0DH
MOV [BX],AL
MOV DX,OFFSET DISP
MOV AH,9 ; 9号功能调用输出显示
INT 21H
POP SI
POP DX
POP BX ;恢复现场
POP AX
RET
PTDN ENDP
第 6章 子 程 序 设 计;统计 CX寄存器所包含权 (在 SI中 )的个数并将其转换成 ASCII码的子程序;入口参数,SI为权码,CX中为给定的数,BX指向 ASCII码结果缓冲区;出口参数:结果缓冲区的当前单元存放转换完的 ASCII码,并且调整 BX
指向缓冲区的下一个单元
QUAN PROC
MOV DL,0 ; DL存放权的个数,初值 0
AI,SUB CX,SI
JC DOWN ;不够减转 DOWN
INC DL ;够减 DL加 1
POP AX
OR AL,1
PUSH AX
JMP AI ;再减权第 6章 子 程 序 设 计
DOWN,ADD CX,SI ;恢复 CX
POP AX
PUSH AX
CMP AL,1
JZ ED
CMP DL,0
JZ EN
ED,MOV AL,30H
ADD AL,DL
MOV [BX],AL ;转 ASCII码并存入缓冲区
INC BX ;指针调整
EN,RET
QUAN ENDP
CODE ENDS
END START
第 6章 子 程 序 设 计讨论:
(1) 排序的优化。源程序中排序子程序使用的是冒泡排序方法,并且使用的是例 5-18给出的优化的冒泡排序法,即比较的轮数是动态的,当到达某一轮后,如果已排好 (标志位没有变化 )则跳出循环,另一方面每一轮中的比较次数是动态的,每一轮比较的次数应该是上一轮的次数减 1,即轮数依次减 1。
(2) 数据范围的限制。本程序只能处理 -32 768~ +32 767
之间的数 (包括界限值 ),如果比较的数不在这个范围中,则需要将程序进行拓展,即将存储二进制数的内存单元定义为双字单元,在数据转换时使用的是寄存器进行运算,在这中间需要注意进位的问题。
第 6章 子 程 序 设 计
(3) 排序算法。该源程序是将输入数据进行降序排列,如果需要变成升序排列,则将子程序 CAMP中的有符号数条件转移指令 JGE变为 JLE即可。
(4) 输入的特殊情况。当输入为 0时,程序会当作只输入一个,-”号来处理,这时在判断输入数据的符号时,需再加一条判断指令:
CMP AL,0
JZ NEXT3
第 6章 子 程 序 设 计
NEXT3后面的语句为 MOV CX,0,在输出时还要加上判断是
0的指令:
CMP CX,0
JZ GO
GO,MOV DL,30H
MOV AH,2
INT 21H
如果在输入正数时不想输入,+”号,那么还需要加判断指令,
即判断输入的第一个字符是否在 1~ 9之间,然后转到 NEXT2。
第 6章 子 程 序 设 计例 6-19 输入两个数 X,Y,在屏幕上显示 xy的值。
分析:首先,要将从屏幕上接收的 x,y的值的 ASCII码转化为二进制数,以便于进一步的运算;其次,判断 x,y是否为 0,若
x=0,则不论 y为何值结果都是 0,若 y=0,则不论 x为何值结果都为 1,这样可直接输出结果,若 x,y均不为 0,则用二重循环的加法运算求得 的值;再次,判断 y是否大于 0,若 y>0,当 x>0
或 x<0且 y为偶数时,,当 x<0且 y为奇数时,;
若 y<0,当 x>0或 x<0且 y为偶数时,?,?当 x<0且 y为奇数时,。 最后,将转化为 ASCII码输出 。
YX
YY XX? YY XX
Y
Y
X
1X?
Y
Y
X
1X
第 6章 子 程 序 设 计
DATA SEGMENT ;定义数据段
BUF1 DB 8 ;存放 X的 ASCII码
DB?
DB 8 DUP(?)
BUF2 DB 8 ;存放 Y的 ASCII码
DB?
DB 8 DUP(?)
CHANGEDB 5 DUP(?) ;存放 BCD码
TEMP DW? ;存放 Y 的绝对值的二进制数
DISP DB 15 DUP(?) ;存放结果的 ASCII码
SIGN1 DB? ; X的符号位
SIGN2 DB? ; Y的符号位第 6章 子 程 序 设 计
HELP1 DB 'PLEASE ENTER THE VALUE OF X,$ ' ;提示符
HELP2 DB 0AH,0DH,'PLEASE ENTER THE VALUE OF Y,$ '
HELP3 DB 0AH,0DH,'THE RESULT IS,$ ',0AH,0DH,'$'
HELP4 DB ')^($ '
HELP5 DB 0AH,0DH,'WARNING:OVER THE DISTINCT!$ ' ;出错提示信息
DATA ENDS
STACK SEGMENT PARA STACK 'STACK' ;定义堆栈段
DB 100 DUP(?)
STACK ENDS
CODE SEGMENT ;代码段
ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
第 6章 子 程 序 设 计
START,MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV DX,OFFSET HELP1 ;提示输入
MOV AH,09H
INT 21H
MOV DX,OFFSET BUF1 ;接收 X的值
MOV AH,0AH
INT 21H
XOR AX,AX
MOV SI,OFFSET BUF1+1
MOV AL,[SI]
ADD SI,AX
INC SI
第 6章 子 程 序 设 计
MOV AL,'$' ; BUF1缓冲区结尾加 $
MOV [SI],AL
MOV SI,OFFSET BUF1+2
MOV AL,[SI]
CMP AL,'0' ;判断 X是否为 0
JNZ NEXT1
INC SI
MOV AL,'$'
MOV [SI],AL
MOV SI,OFFSET DISP ; DISP缓冲区写入 0
MOV AL,30H
MOV [SI],AL
第 6章 子 程 序 设 计
INC SI
MOV AL,'$'
MOV [SI],AL
JMP NEXT2
NEXT1,MOV SI,OFFSET BUF1+1
MOV DI,OFFSET SIGN1
CALL GETD
PUSH CX ; X绝对值的二进制数入栈
NEXT2,MOV DX,OFFSET HELP2 ;提示输入
MOV AH,09H
INT 21H
MOV DX,OFFSET BUF2 ;接收 Y的值
MOV AH,0AH
INT 21H
第 6章 子 程 序 设 计
XOR AX,AX
MOV SI,OFFSET BUF2+1 ; BUF1缓冲区结尾加 $
MOV AL,[SI]
ADD SI,AX
INC SI
MOV AL,'$'
MOV [SI],AL
MOV SI,OFFSET DISP
MOV AL,[SI]
CMP AL,'0' ; X是否已经为 0
JZ DIS
MOV SI,OFFSET BUF2+2
MOV AL,[SI]
CMP AL,'0' ; Y是否等于 0
第 6章 子 程 序 设 计
JNZ NEXT3
INC SI
MOV AL,'$'
MOV [SI],AL
MOV SI,OFFSET DISP
MOV AL,31H
MOV [SI],AL
INC SI
MOV AL,'$'
MOV [SI],AL
JMP DIS
NEXT3,MOV SI,OFFSET BUF2+1
MOV DI,OFFSET SIGN2
第 6章 子 程 序 设 计
CALL GETD
MOV TEMP,CX ; Y绝对值的二进制数送临时缓冲区 TEMP
MOV DX,CX ; DX中为 Y绝对值的二进制数
POP CX
MOV AX,CX ; AX,CX中为 X绝对值的二进制数
CALL FUNC
MOV CX,AX ;的值送 CX
MOV AL,SIGN2
CMP AL,1 ; Y是否为负数
JZ NEXT4
CALL PTDN
JMP DIS
NEXT4,CALL NEGT
第 6章 子 程 序 设 计
DIS,MOV DX,OFFSET HELP3 ;提示输出
MOV AH,09H
INT 21H
MOV DL,' ('
MOV AH,02H
INT 21H
MOV DX,OFFSET BUF1+2 ;显示 X的值
MOV AH,09H
INT 21H
MOV DX,OFFSET HELP4
MOV AH,09H
INT 21H
MOV DX,OFFSET BUF2+2 ;显示 Y的值
MOV AH,09H
INT 21H
第 6章 子 程 序 设 计
MOV DL,') '
MOV AH,02H
INT 21H
MOV DL,'='
MOV AH,02H
INT 21H
MOV DX,OFFSET DISP ;显示最终结果
MOV AH,09H
INT 21H
EXIT,MOV AH,4CH
INT 21H;将 X和 Y的绝对值转换成二进制数并将符号位存入 DI指向单元的子程序;入口参数,SI,DI;出口参数,CX
第 6章 子 程 序 设 计
GETD PROC
PUSH AX
PUSH BX
PUSH DX
MOV BL,[SI] ;实际输入的字符个数送 BL
INC SI
MOV AL,[SI] ;判断是否有符号位
CMP AL,'-'
JNZ GO1 ;若实际符号位为负
MOV AL,1 ;则符号单元送 1
MOV [DI],AL
DEC BL
INC SI; ASCII码转换成 BCD码第 6章 子 程 序 设 计
GO1,MOV DI,OFFSET CHANGE
PUSH DI
PUSH BX ;保存字符个数
GO2,MOV AL,[SI]
AND AL,0FH
MOV [DI],AL ; ASCII码转换成 BCD码送到 CHANGE开始的单元
INC DI
INC SI
DEC BL ;字符个数减 1
JNZ GO2 ;若不等于 0,继续转换
POP BX; BCD码转换成二进制码
POP DI
MOV CX,0 ;累加和置初值第 6章 子 程 序 设 计
AG1,PUSH BX
ADD CX,CX
MOV BX,CX
ADD CX,CX
ADD CX,CX
ADD CX,BX ;累加和 *10
MOV BL,[DI]
MOV BH,0
INC DI
ADD CX,BX ;累加和 *10后加下一个 BCD数字
POP BX
DEC BL
JNE AG1 ;未转换完时继续
POP DX
POP BX
POP AX
RET
第 6章 子 程 序 设 计
GETD ENDP; X和 Y转换成二进制数后求 |X||Y|的子程序;入口参数,AX,CX,DX;出口参数,AX
FUNC PROC
PUSH BX ;保护现场
PUSH SI
PUSH DI
PUSH CX
LOP1,POP CX
PUSH CX
MOV BX,AX
DEC DX
JZ EN ;外层循环是否结束第 6章 子 程 序 设 计
LOP2,DEC CX
JZ LOP1 ;内层循环是否结束
ADD AX,BX ;累加
JNO GG ;判断是否溢出
MOV DX,OFFSET HELP5 ;溢出提示信息
MOV AH,09H
INT 21H
JMP EXIT
GG,JMP LOP2
EN,POP CX ;恢复现场
POP DI
POP SI
POP BX
RET
FUNC ENDP
第 6章 子 程 序 设 计; Y>0时 XY的 ASCII码存入 DISP开头缓冲区内的子程序;入口参数,CX中为待处理的数据;出口参数:结果的 ASCII码存入 DISP开头的缓冲区中;所用子程序,QUAN
显示程序用 PTDN。子程序 PTDN和 QUAN与例 6-18中的 PTDN和 QUAN完全一样,
这里就不再赘述了,请参见例 6-18的描述; Y<0时 XY的 ASCII码存入 DISP开头缓冲区内的子程序;入口参数,CX中为待处理的数据;出口参数:结果的 ASCII码存入 DISP开头的缓冲区中第 6章 子 程 序 设 计
NEGT PROC
PUSH AX
PUSH BX
PUSH DX
PUSH SI
PUSH DI ;保护现场
MOV DI,OFFSET DISP ; DI指向缓冲区 DISP
MOV AL,SIGN1
CMP AL,1 ; X是否为负数
JNZ NU
MOV SI,OFFSET TEMP
MOV DX,[SI]
CLC
RCR DX,1 ; Y是否为偶数
JNC NU
MOV AL,'-'
MOV [DI],AL ;缓冲区存入负号
INC DI
第 6章 子 程 序 设 计
NU,MOV AL,30H
MOV [DI],AL
INC DI
MOV AL,','
MOV [DI],AL
INC DI
MOV BL,0
MOV SI,CX
MOV CX,10 ;转换精度
MOV AX,1 ;求倒数的第一个被除数为 1
PUSH BX
第 6章 子 程 序 设 计
LO,PUSH CX
ADD AX,AX
MOV BX,AX
ADD AX,AX
ADD AX,AX
ADD AX,BX ;被除数乘 10
MOV CL,0 ; CL存放权的个数,初值 0
TN,SUB AX,SI
JC DOWN ;乘完减除数,不够减转 DOWN
INC CL ;够减 CL加 1
POP DX
POP BX
OR BL,1 ;有一个权的个数已经不为 0
PUSH BX
PUSH DX
JMP TN
第 6章 子 程 序 设 计
DOWN,ADD AX,SI ;恢复 AX
ADD CL,30H
MOV [DI],CL ;将权值的 ASCII码存入缓冲区
INC DI
CMP AX,0 ;已经除尽
JZ EX
POP CX
LOOP LO
POP BX
CMP BL,0 ;是否有至少一个权的个数不为 0
JNZ EX
MOV DI,OFFSET DISP ;如果所有的权的个数都为 0,则显示 0
MOV AL,30H
MOV [DI],AL
INC DI
第 6章 子 程 序 设 计
EX,MOV AL,'$'
MOV [DI],AL
POP DI
POP SI
POP DX
POP BX
POP AX ;恢复现场
RET
NEGT ENDP
CODE ENDS
END START
第 6章 子 程 序 设 计讨论:
(1) 特殊情况的处理。如果 X=0,Y为负数,则结果应该趋于无穷大,而本程序的结果为 0。如果要将这一特殊的与实际不符的情况排除,应该在判断 X=0后跳到如下代码:
MOV SI,OFFSET BUF2+2
MOV AL,[SI]
CMP AL,'-'
JNZ DIS
MOV DI,OFFSET DISP
MOV AL,'-'
MOV [DI],AL
INC DI
第 6章 子 程 序 设 计
MOV AL,'>'
MOV [DI],AL
INC DI
MOV AL,'$'
MOV [DI],AL
MOV DX,DISP
MOV 09H
INT 21H
MOV DX,HELP6
MOV 09H
INT 21H
HLT
如果要将上述程序段加入的话,应先在数据段增加定义:
HELP6 DB 'INFINITY$'。
第 6章 子 程 序 设 计
(2) 用户误操作的处理。如果用户输入的不是数字也不是
‘ -’号,则程序应该予以提示,要完成此功能应该增加如下代码:
MOV SI,OFFSET BUF1
MOV CX,[SI+1]
ADD SI,2
LP,CMP [SI],30H
JBE GO1
CMP [SI],39H
JAE GO2
第 6章 子 程 序 设 计
GO1,CMP [SI],28H
JNZ GO2
INC SI
LOOP LP
JMP NT
GO2,MOV DX,OFFSET HELP7
MOV AH,09H
INT 21H
HLT
第 6章 子 程 序 设 计如果要将上述程序段加入的话,应先在数据段增加定义:
HELP7 DB 'INPUT ERROR! $';此段代码应放在用 10号功能调用读入 X的值的代码后面,如果要判断 Y的输入,只需将代码中的 BUF1改为 BUF2;标号 NT 指向在源程序中 10号功能调用读入
X值的下一条代码。
第 6章 子 程 序 设 计
(3) 局限性。本程序只能处理 在 -32 768~ +32 767之间的情况,如果超出这个范围则显示 WARNING:OVER THE
DISTINCT!。如果需要将结果范围拓展则需要进一步进行判断。
(4) 精度的取法。当 X不等于 0且 Y小于 0时,结果应该为小数。本程序对于小数精度的处理方法是:精度取为 10,第 11位舍掉,如果用户想对第 11位四舍五入,只需将 10次的减法改为
11次,判断第 11位如果大于 5,则将第 10位的数加 1,再在第 11
位所处空间的内容后添入 $;如果第 11位小于 5,则直接将第 11
位所处空间的内容后添入 $。
YX
第 6章 子 程 序 设 计
(5) 标志位的设置。本程序中有一关键性的技巧,那就是标志位的设置。在程序中,标志位共出现两次:一次是在 QUAN
子程序中,BH为标志位,用处是看权值由 10000变为 10的过程中是不是所有的权的个数都为 0,如果 BH一为 0,则送 30H到 DI所指的地址;如果不是,则按普通情况处理;另一次是在 NEGT子程序中,BL为标志位,用处是看小数的每一位是不是都是 0,如果是,则直接将 30H写入 DI所指的地址,而不写入 0000000000的
ASCII码。
第 6章 子 程 序 设 计习 题 六
6.1 编程序重复接收学生分数 (用 A,B,C,D表示 )且自动汇总各种分数的人数,并输出汇总结果。若按下 &键,表示输入结束。按下 A,B,C,D以外的任一键都无效,并提示 ERROR。
(使用子程序编制,各段定义要求完整 )。
6.2 子程序文件说明中应包括哪几方面的内容?为什么?
6.3 编程序一边从键盘上接收字符,一边将其写入指定的文件,字符个数共 60,并将其显示在屏幕上。
第 6章 子 程 序 设 计
6.4 编写子程序实现两个多字节未压缩 BCD码相乘。
入口参数,SI指向乘数首地址,DI指向被乘数首地址,BX
指向乘积首地址,乘数的长度由 OPRD指定。
出口参数,SI指向乘数首地址,DI指向被乘数首地址,BX
指向乘积首地址。
第 6章 子 程 序 设 计
6.5 用子程序编制求解下列问题的程序:
(1) 将两个多字节压缩 BCD码相加,并把结果显示出来;
(2) 将字节单元中的二进制数转换成十进制数输出;
(3) 在字符串中第 n个字符前插入一个字符;
(4) 删除字符串中从第 n个字符开始的 m个字符;
(5) 实现若干有符号数的排序操作。