




已阅读5页,还剩179页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
子程序设计,第五章,在汇编语言中,子程序又称为过程,是程序设计中的一种重要方法。在实际的程序中,常常会遇到在同一程序或多个程序中多次遇到同一任务的处理过程。如果每一次处理都编写一次程序,不仅加大了编程人员的工作量,也浪费存储空间。,为了避免重复编程,人们将经常遇到的处理任务编写成一些独立的程序段,以供其他程序调用,这就是子程序设计。为了能更好地把握子程序的编写和使用方法,必须先掌握堆栈的有关概念。,5.1堆栈,5.2子程序的调用与返回,5.3子程序设计,5.4子程序设计案例,习题5,5.1堆栈,在汇编语言和机器语言中,堆栈在物理结构上是一段存放数据的连续的内存区域,以及一个称为栈顶指针的专用存储单元。,堆栈中只能存入16位的字型数据,存入数据的操作称为“进栈”或“压栈”,已存入的数据也可以取出,称为“出栈”或“弹出”,数据的存取操作由专用指令完成。从逻辑上说,堆栈是一种按“先进后出”或“后进先出”原则进行操作的数据结构,栈顶指针用于指出入栈操作和出栈操作的位置。,5.1.1建立堆栈,图5-1是堆栈的物理结构示意图.图中标出的SS和SP是与堆栈密切相关的寄存器,SS存放堆栈所占用内存区域的段地址,SP所指向的位置称为栈顶。,图5-1堆栈的逻辑结构图,一个程序如果要使用堆栈,首先要建立堆栈。建立堆栈实际上就是在程序中定义一个堆栈段,并给SS和SP赋值即可。定义堆栈段的格式如下:段名SEGMENTSTACKDWnDUP(?)段名ENDS,【说明】(1)保留字STACK是堆栈段的专用符号,SEGMENT后面的保留字STACK表明这个段专供堆栈使用。(2)段定义中用“DWnDUP(?)”说明堆栈所用内存区的大小为2n字节,其中n是一个常量。可根据程序需要,调节堆栈段的大小。因为堆栈只能存放字型数据,所以习惯上都是用DW伪指令来定义栈的大小。这不并是说用其它伪指令不行。,(3)按基本格式定义的栈是一个空栈,栈中没有存放有效数据。(4)为了使SS和SP在程序执行时取得正确的值,必须在源程序中写一条伪指令:ASSUMESS:堆栈段段名,但不需要像DS和ES一样在程序中用指令进行赋值。对SS和SP的赋值是由操作系统在把执行程序调入内存时由DOS本身完成的,DOS将把SS赋值为堆栈段的段地址,把SP赋值为2n,这时用户使用的就是系统堆栈。当然,用户也可以根据程序的需要按自己的要求定义自己的堆栈,这时,用户不仅要定义堆栈段,而且还要用指令给SS和SP赋值。,5.1.2堆栈操作指令,栈操作指令以它特有的方式存取数据,属于数据传送类指令,但又与MOV等指令有很大的区别。1.进栈指令【指令格式】PUSHS【功能】先把SP的值减去2,然后把操作数S指明的字型数据放入以SS为段地址、SP为偏移地址所对应的字内存单元中。,【说明】(1)这是单操作数指令,操作数S可以是包括段寄存器在内的任何字型寄存器,或者内存型寻址方式,但不能是立即寻址。(2)PUSH指令的功能包括移动栈顶和存入数据两部分,两部分连续完成,密不可分。,(3)操作数S进栈是以减2以后的SP的值作为偏移地址,但程序中不允许出现SP的写法。不要与基地址寄存器或变址寄存器用作偏移地址时的写法相混淆,也就是说,把PUSH指令理解成下面两条指令的组合是不正确的:SUBSP,2MOVSP,S因为指令“MOVSP,S”存在语法错误。,(4)PUSH指令会导致栈顶指针的移动,如果用PUSH指令把很多数据进栈,使SP不断减2,就有可能超出栈的有效范围。在一些高级语言中这种现象会导致堆栈溢出错误,但8086对此并不做任何检测和警告。因此要求编程人员自己注意控制堆栈的大小,估计可能进栈的数据量,以免由于栈溢出导致一些不可预测的后果。,假设:SS=3000H,SP=0200H,AX=1234H,则CPU执行PUSHAX指令后:SS的内容不变,SP=01FEH,栈顶字单元(301FEH)=1234H,AX的内容不变。,2出栈指令【指令格式】POPD【功能】从SS为段地址、SP为偏移地址的栈顶单元中取出一个字型数据,送到操作数D指定的位置,然后把SP的值加2。对操作数D的寻址方式要求与PUSH指令相同。,堆栈通常用于临时保存数据。一般做法是先用PUSH指令把需要保存的数据入栈,然后完成一定的指令序列,再用POP指令把原先保存的数据出栈。用堆栈保存数据的特点是不用定义变量,不必关心被保存的数据到底在栈的什么位置,只要保证出栈和进栈的对应关系即可。当CPU中的寄存器不够使用时经常用堆栈临时保存数据。,假设:SS=3000H,SP=0200H,栈顶单元的数据为1234H,即(30200H)=1234H,则CPU执行POPAX指令后,SS的内容不变,SP=0202H,AX=1234H。栈顶所指位置以上的部分是堆栈的空闲区,以下部分是已入栈的数据存放区(见图5-1),例5-1用来说明PUSH指令和POP指令对堆栈的影响。,【例5-1】设AX1234H,BX56578H,SP1000H,分别逐条执行下列指令,用内存图的形式画出堆栈的变化情况,并分析程序段执行完后AX和BX寄存器的值。PUSHAXPUSHBXPOPAXPOPBX,【解】堆栈变化见图5-2,程序段执行完后AX5678H,BX1234H。,(a)执行前,(b)执行PUSHAX后,(c)执行PUSHBX后,(d)执行POPAX后,(e)执行POPBX后,图5-2执行PUSH和POP指令前后堆栈的变化情况,3.标志寄存器入出栈指令(1)标志寄存器入栈指令【指令格式】PUSHF【功能】把SP的值减2,并把16位的标志寄存器的内容送入SS:SP所指向的内存字单元,即把标志寄存器的值入栈。,(2)标志寄存器出栈指令【指令格式】POPF【功能】把栈顶的一个16位的字型数据从堆栈中弹出,然后送入标志寄存器,并把SP的值加2。,这两条指令相互配合可以设置标志寄存器中的任意一个标志位,一般的做法是:PUSHFPOPAX;按标志位的分布情况和实际需要,修改AX中的值PUSHAXPOPF,5.2子程序的调用与返回,把可以多次调用、能够完成特定处理任务的程序段编写成独立的程序模块,称为子程序。子程序可以被其他程序调用,调用这些子程序的程序称为主程序。,在主程序中,如果调用子程序,就把控制转移到子程序,这个过程称为转子;子程序执行完后,再把控制返回到主程序,这个过程称为返主。主程序与子程序之间的转换关系如图5-3所示。,图5-3主程序和子程序间的转换关系,为了实现主程序调用子程序以及子程序执行完后能返回主程序,在8086/8088指令系统中提供了一组调用指令CALL和返回指令RET,5.2.1子程序调用指令CALL,主程序调用子程序需要通过调用指令来实现。调用指令的基本功能是将返回地址,即调用指令的下一条指令的偏移地址或下一条指令的段地址和偏移地址(简称为断点)压入堆栈,并按照某种寻址方式转向子程序的入口。,主程序和子程序可以在同一个代码段中,也可以在不同的代码段中。前者称为段内调用,只涉及偏移地址,后者称为段间调用,将涉及段地址和偏移地址两部分。,段内调用指令的基本功能是:将断点地址,即当前的IP值压入堆栈,段寄存器CS的内容保持不变。然后将子程序的入口地址(即偏移地址)装入IP中。段间调用指令执行时,其断点地址及子程序的入口地址包括段地址和偏移地址。,子程序入口地址直接出现在调用指令中(用子程序名表示),称为直接调用;子程序入口地址存放在寄存器或存储单元中,而寄存器名或存储单元的地址出现在调用指令中,称为间接调用。,1.段内直接调用【指令格式】CALLDST或:CALLNEARPTRDST;DST为子程序名或语句标号,执行操作:(SP)(SP)2;即修改堆栈指针;(SP+1),(SP)(IP);IP(断点)入栈;(IP)(IP)+16位偏移量;转向子程序入口。,说明:该指令的第一步是把子程序的返回地址(即CALL指令的下一条指令地址)压入堆栈中,第二步是转移到子程序的入口地址,执行该子程序。,指令中的DST为子程序的入口地址,16位偏移量是子程序入口地址与CALL指令的下一条指令地址之间的差值,所以这种调用也称为相对调用。,【例5-2】指令CALLSUB1SUB1为子程序名,即过程名或语句标号,且子程序SUB1被定义为段内标号或近过程名,则CALLSUB1就是段内直接调用。,若假设指令CALLSUB1的第一个字节的地址为2000H:1000H,段内直接调用指令为三字节,因此其返回地址是2000H:1003H。再设子程序SUB1的入口地址为2000H:3000H,那么,该指令执行后,堆栈、IP和CS的内容如图5-4所示。,图5-4段内直接调用示意图,2.段内间接调用【指令格式】CALLDST或:CALLWORDPTRDST;DST为通用字寄存器或字存储单元,执行操作:1.(SP)(SP)2;修改堆栈指针2.(SP+1),(SP)(IP);IP入栈3.(IP)(DST);转向子程序,说明:该指令与段内直接调用类似,区别是将字寄存器或存储单元内容作为子程序入口地址送入IP。,【例5-3】下面三种情况均是段内间接调用。CALLBXCALLWORDPTRBXCALLES:WORDPTRSUB2,3.段间直接调用【指令格式】CALLFARPTRDST;DST为子程序名或语句标号,执行操作:1.段点入栈:(SP)(SP)2(SP+1),(SP)(CS);(CS)入栈;(SP)(SP)2(SP+1),(SP)(IP);(IP)入栈;,2.子程序入口地址送IP和CS,转入子程序:(IP)偏移地址;IP由指令中的偏移地址取代;(CS)段地址;CS由指令中的段地址取代.,说明:该指令的操作与段内直接调用指令基本类似,不同的只是:其一,调用时不仅要保护偏移地址,而且要保护段地址,它们的顺序是CS先压栈,然后再把IP压栈;其二,CS和IP的值,是直接将子程序入口的段地址和偏移地址送入CS和IP中。,【例5-4】指令CALLFARPTRSUB2SUB2是子程序名,且过程SUB2与调用指令不在同一个代码段,即段间直接调用若设该指令的第一个字节的地址为2000H:0200H,段间直接调用指令为五字节,因此,其返回地址为2000H:0205H。再设子程序SUB2的入口地址为4000H:0100H,那么,该指令执行后,堆栈、IP和CS的内容如图5-5所示。,图5-5段间直接调用示意图,4.段间间接调用【指令格式】CALLDWORDPTRDST;DST为双字存储器单元操作数,执行操作:1.段点入栈:(SP)(SP)2(SP+1),(SP)(CS);CS入栈(SP)(SP)2(SP+1),(SP)(IP);IP入栈,2.子程序入口地址送IP和CS,转入子程序:(IP)(EA);指令中的有效地址所指字内容送入IP;(S)(EA+2);指令中的有效地址+2所指字内容送入CS.,说明:该指令的操作与段间直接调用指令基本类似。不同的是根据寻址方式求出EA后,把指定存储器单元的字内容送到IP寄存器,再把下一个字的内容送到CS寄存器。,【例5-5】下面指令都是段间间接调用。CALLDWORDPTRBXCALLDWORDPTRSADR上面两条指令的操作数均为双字存储器类型。其中第一指令是由BX的内容指定数据段中的一个双字存储单元地址,而第二条指令是由SADR指定数据段中的一个双字存储单元地址。它们所指的字单元存放着子程序的入口偏移地址,下一个字单元存放着子程序的入口段地址。,注意:调用的类型与子程序/过程定义时的类型必须一致。,5.2.2子程序返回指令RET,子程序执行完后需要返回主程序,这个功能由子程序返回指令RET来实现。返回指令总是与调用指令配合使用,且返回指令通常放在子程序的末尾(即出口处),使子程序执行完毕能够返回主程序,继续执行原来的程序。,与调用指令相对应,返回指令也有段内返回和段间返回两种形式。其功能是从堆栈的栈顶弹出返回地址。段内返回是从栈顶弹出一个字数据送入IP;段间返回是从栈顶弹出两个字数据分别送入IP和CS,从而控制程序返回到主程序。,1、段内返回指令【指令格式】RET执行操作:(IP)(SP+1),(SP);栈顶字内容弹出送入IP;(SP)(SP)+2,说明:当子程序被定义为近过程时,RET指令执行的操作是段内返回,它把堆栈顶部的一个字内容送入IP,作为返回的偏移地址。,2、段间返回指令【指令格式】RET执行操作:(IP)(SP+1),(SP);栈顶内容送入IP(SP)(SP)+2;修改堆栈指针(CS)(SP+1),(SP);栈顶内容送入CS(SP)(SP)+2;修改堆栈指针,说明:当子程序被定义为远过程时,RET指令执行的操作是段间返回,它把堆栈顶部的两个字内容依次送入IP和CS,分别作为返回的偏移地址和段地址。,3、带参返回指令指令格式:RETn;n为立即数或数值表达式,说明:1、带立即数返回指令分为带立即数的段内返回和带立即数的段间返回两种形式。它们都是在完成基本返回功能后,再执行:(SP)(SP)+n的操作,即修改堆栈指针。2、n一般是偶数,主要用以废弃堆栈中的一些数据。,【例5-6】用带参返回指令RET4,跳过子程序调用前传入的参数。MOVAX,M1PUSHAX;参数M1被压栈MOVAX,M2PUSHAX;参数M2被压栈CALLSUBPROC,SUBPROC:RET4;带参返回在上面的程序段中,调用SUBPROC子程序前,通过两次压栈将两个参数M1和M2传递给子程序使用,而在子程序执行完后,这两个参数也就没用了,所以必须废弃它们。为此,采用带参返回指令,使栈顶指向第一条PUSH指令执行以前的位置。,5.3子程序设计,子程序是供主程序调用的。为了使不同的程序在不需要了解子程序的内部结构及其算法情况下,方便地调用子程序,一个完整的子程序应当包括:子程序调用方法说明、保护现场和恢复现场、子程序定义、参数传递方法等部分。,本小节主要介绍子程序调用方法说明与保护现场和恢复现场,对于子程序定义、参数传递方法将放在后面小节作专题介绍。,1、子程序调用方法说明为了使子程序便于阅读、维护和使用,以及明确子程序的功能和主程序与子程序之间的联系,让使用者完全不必关心所用子程序的算法及处理过程,一般应提供子程序调用方法说明。主要包含下述几项内容:,1.子程序名:供调用子程序时使用;2.子程序功能:用来指明该子程序完成什么样的操作或实现何钟功能,供选择子程序时参考;3.入口参数:说明调用子程序前应该把什么样的数据放在什么地方;,4.出口参数:说明调用后从什么地方取得处理结果;5.保护的寄存器:指出子程序中哪些寄存器受到保护,哪些寄存器的内容被破坏。,【例5-7】编写一个子程序,对一个无符号的字型数组的各元素求和。在调用子程序之前,已把数组的段地址放在DS中,起始偏移地址放在寄存器SI中,数组元素个数(0)放在CX中。要求子程序把计算结果以双字的形式存放,高位放在DX中,低位放在AX中。并为该子程序编写调用方法说明。,【解】;子程序名:Sum;子程序功能:对字型数组各元素求和,结果是双字;入口参数:DS:SI:数组的逻辑地址,CX:数组元素个数;出口参数:DX,AX:各数组元素的和,DX为高16位,AX为低16位;保护的寄存器:BX;破坏的寄存器:CX,sumPROCNEARPUSHBX;保护寄存器BXXORAX,AXMOVDX,AX;DX,AX清0MOVBX,AXs1:ADDAX,BX+SI;累加ADCDX,0;加进位INCBX;修改数组地址,INCBX;修改数组地址LOOPs1;循环控制POPBX;恢复寄存器BXRET;返回sumENDP子程序调用方法说明一般提行注释,并放在子程序的前面。,2、保护现场和恢复现场保护现场和恢复现场是子程序设计时必须考虑的问题。子程序中需要使用的寄存器,有可能在调用子程序前主程序正在使用,其值在从子程序返回主程序后还要继续使用,我们把这些寄存器的值和状态寄存器的状态均称之为现场。显然,子程序执行前需要保护现场,返回时要恢复现场。,保护和恢复现场的工作可以在主程序中完成,也可以在子程序中完成。一般情况下,是在子程序的开始安排一串保护现场语句,子程序返回前恢复现场。这样处理,主程序在转子前后均不必考虑保护和恢复现场的工作,其处理流程显得清晰。,保护和恢复现场,更简洁的方法是利用压栈指令,将寄存器内容及状态标志寄存器的内容压入堆栈,恢复时再从堆栈中弹出。尤其在嵌套子程序设计中,由于压栈和出栈指令会自动修改堆栈指针,保护和恢复现场的工作层次清晰,只要注意堆栈操作的先进后出原则,就不会造成混乱和错误。,【例5-8】使用堆栈保护和恢复现场,程序如下:SUBRPROCPUSHAX;保护现场PUSHBXPUSHCXPUSHDX;子程序处理部分语句,POPDX;恢复现场POPCXPOPBXPOPAXRET;返回SUBRENDP注意:恢复现场时寄存器出现的顺序应与保护现场时寄存器出现的顺序相反。,5.3.2子程序的定义,汇编语言中,子程序也叫过程。其定义格式为:过程名PROCNEAR/FARRET过程名ENDP,说明:(1)过程名用以标识不同的过程。过程名是提供给其他程序调用时使用的,因而不能省略。过程名具有与语句标号相同的属性,即具有段地址、偏移地址和类型三个属性。过程名的段地址和偏移地址是指过程中第一条可执行指令的段地址和偏移地址,过程名的类型由格式中的NEAR/FAR指定。,(2)PROC与ENDP相当于一对语句括号,将子程序的处理部分(也称过程体)括在其内。过程体为一段相对独立的程序,是完成子程序功能的程序主体。(3)NEAR或FAR是过程的类型说明参数。NEAR类型的过程只允许段内调用;FAR类型的过程允许段间调用,即允许其它段的程序使用。过程的缺省类型为NEAR。,(4)过程的属性决定了调用指令CALL和返回指令RET的操作。对于FAR类型的过程应采用段间调用指令格式,相应的返回指令为段间返回指令;而对于NEAR类型的过程应采用段内调用指令,其返回指令为段内返回指令。,例5-9】主程序和子程序在同一代码段中的调用,程序如下:CODESEGMENT;CODE段定义CALLSUBR1;段内调用SUBR1,SUBR1PROCNEAR;近过程定义RET;近过程返回SUBR1ENDPCODEENDS,由于主程序MAIN和子程序SUBR1是在同一代码段中,所以SUBR1定义为NEAR属性。这样MAIN中对SUBR1的调用和SUBR1中的RET就都是NEAR属性了。注意:以上程序是将子程序放置在主程序的后面,然而也可以将子程序放在主程序的前面。,【例5-10】主程序和子程序不在同一个代码段中的调用,程序如下:SEG1SEGMENT;SEG1段定义SUBR2PROCFAR;远过程定义RET,SUBR2ENDPSEG1ENDSSEG2SEGMENT;SEG2段定义CALLFARPTRSUBR2;段间调用SEG2ENDS,由SEG1和SEG2分别定义了两个段,调用指令位于SEG2段,而过程定义位于SEG1段。也就是说,主程序和子程序不在同一个代码段中,所以SUBR2被定义为FAR类型,同样对SUBR2的调用CALL和SUBR2中的RET都是FAR类型。,(5)使子程序即可被本代码段使用,又可被其它代码段使用,则该子程序必须定义为FAR类型,它的返回指令被定义为段间返回。【例5-11】子程序被不同段中的调用程序调用,程序如下:SEG1SEGMENT;SEG1段定义SUBSPROCFAR;远过程定义,RETSUBSENDPCALLFARPTRSUBS;段内调用SEG1ENDS,SEG2SEGMENT;SEG2段定义CALLFARPTRSUBS;段间调用SEG2ENDS,过程SUBS被两处调用,一处是与它在同一段SEG1段内,另一处是在另一段SEG2段内,为此SUBS必须具有FAR类型,以适应SEG2段调用的需要。SUBS具有FAR类型后,不论在SEG1或SEG2段,对SUBS的调用都应是FAR类型。,(6)被定义为过程的程序块中应该有返回指令RET,但不一定是最后一条指令,也可以有不止一条RET指令,只要执行RET指令,程序就返回到原来调用指令的下一条指令处。,(7)当操作系统把控制权交给用户程序时,在程序段前缀的开始(偏移地址为0)处安排了一条中断返回指令INT20H,DS中为程序段前缀的段地址,为让程序执行结束能正常返回操作系统,经常把主程序定义为一个远过程,并且主程序开始先使用以下三条指令:,PUSHDSMOVAX,0PUSHAX主程序最后,使用RET指令返回。,5.3.3带参数的子程序,子程序的功能往往与数据处理有关。通常,子程序在编写时并不知道需要处理的数据是多少,只知道被处理的数据是什么形式,包括被处理数据的类型、数量和存放形式。子程序总是以同一种模式对不同的数据进行处理。,调用这一类子程序时,需要先告诉它被处理的数据是多少,放在什么位置,这种被处理的数据称为子程序的入口参数。子程序把接收到的数据进行处理,处理的结果一种情况是送到显示器、打印机等输入设备上,另一种情况,子程序还要把处理结果通知它的调用者。由子程序传递给调用者的数据称为子程序的出口参数。,参数是子程序与调用者之间数据传递的途径,子程序与调用者之间必须达成一致,把参数放在双方都能取到的地方。总体来说,不论是调用者传递给子程序的入口参数,还是子程序返回给调用者的出口参数,传递的方式常用以下5种。,1.用通用寄存器传送数值如果需要传递的数据量不大,比如一个字、一个字节,就可以用某个通用寄存器作为数据的载体。例如,在【例5-7】中,入口参数有两部分,其中数组元素个数是一个简单数据,使用CX寄存器进行传递,出口参数是32位的双字,使用两个寄存器DX和AX进行传递,2.用寄存器传送逻辑地址通用寄存器能够存放的数据量是有限的。当需要传递的数据量较大时,可以把数据放在一段连续的内存区域中,然后把逻辑地址放在两个16位的寄存器中。通常是把段地址部分放在DS或ES中,偏移地址则放在一个16位的通用寄存器中。在【例5-7】中的入口参数中有一个字型数组的逻辑地址,就是采取的这种参数传递方式,数组本身放在内存中,而把数据的逻辑地址放在段寄存器DS和通用寄存器SI中。,3.用标志寄存器传送逻辑型数据只有“是”或“非”两种情况的数据是逻辑型数据,表示这种数据只需要一个二进制位就够了。一个二进制位如果要用于存放逻辑型数据,还要具备一定的条件:能够比较容易地在这个位上设置逻辑值,也能较容易地取出它的值进行处理。在8086系统中,标志寄存器中的CF标志位符合这一要求。,对CF的处理方法有JC、JNC、ADC、SBB等指令。而对CF的设置除了影响条件标志位的那些指令之外,还有下列三条专用指令。,【指令格式】CLC【功能】对CF标志位清0。【指令格式】STC【功能】对CF标志位置1。【指令格式】CMC【功能】对CF标志位的原值取反。,【例5-12】编写一个子程序,以放在AX中的公元年份为入口参数,判断该年是否为闰年。另有一个应用程序,它已定义了一个字节型数组t,依次存放着12个月的每月天数,其中2月份的天数是28。应用程序已经在DX中存放了年份值,利用前面编写的子程序,编写程序段调整数组t中2月份的天数。,【分析】题目中已明确入口参数必须放在AX中,而出口参数并没有指定存放位置。由于子程序的功能是完成一个判断操作,结果只有“是”或“非”两种可能,是逻辑值,可以置于CF位。,【解】子程序清单如下:;子程序名:Jud;功能:判断一个年份是否为闰年;入口:AX=公元年份;出口:CF=1表示闰年,CF=0表示非闰年;破坏寄存器:AX,JudPROCNEARPUSHBXPUSHCXPUSHDXMOVCX,AX;临时保存年份值MOVDX,0MOVBX,4DIVBX,CMPDX,0JNZlab1MOVAX,CXMOVBX,100DIVBXCMPDX,0JNZlab2MOVAX,CX,MOVBX,400DIVBXCMPDX,0JZlab2Lab1:CLCJMPlab3Lab2;STCLab3:POPDX,POPCXPOPBXRETJudENDP,对于DX中存放的年份值,需要先放到AX中,才能调用子程序jud,,然后以调用返回后的CF值,决定是否把t数组中表示2月份天数的t+1单元加1,程序段如下:MOVAX,DXCALLjudADCBYTEPTRt+1,0;原值+0+CF,4.用数据段中定义的变量传送参数用数据段中定义的变量作为参数传送的载体也是一种常用方法。这种方法要求子程序与调用者之间约定好以哪个变量或哪几个变量进行参数传送。,具体做法是:对于用作入口参数的变量,调用者在调用子程序的CALL指令之前,先把变量赋以一定的值,然后以CALL指令转到子程序执行,子程序则取出该变量中的数据进行处理;对用作出口参数的变量,也有赋值与取值两个阶段,子程序进行数据处理后,把处理结果放到约定好的变量中,然后以RET指令返回调用者,调用者可以从变量中取出处理结果使用。,【例5-13】用变量传送参数,编写【例5-12】要求的子程序。调用该子程序的条件为:在调用程序中已定义了字型变量year和字节型变量day,且字型变量year已赋公元年份值。,【解】;子程序名:Jud1;功能:根据一个年份是否为闰年,设置该年2月份的天数;入口:DS段中的字型变量year=公元年份;出口:DS段中的字节型变量day=该年2月份天数;破坏寄存器:无。,Jud1PROCNEARPUSHAXPUSHBXPUSHCXPUSHDXMOVBYTEPTRday,28MOVAX,yearMOVDX,0MOVBX,4,DIVBXCMPDX,0JNZlab1MOVAX,yearMOVBX,100DIVBXCMPDX,0JNZlab2MOVAX,yearMOVBX,400,DIVBXCMPDX,0JNZlab1Lab2:INCBYTEPTRdayLab1:POPDXPOPCXPOPBXPOPAXRETJud1ENDP,对于【例5-13】的子程序,调用前需要先把公元年份值(入口参数)放到指定的变量year中,调用返回后,可以从变量day中取得结果,即二月份的天数(出口参数)。,5.用堆栈传送参数参数传送不仅要在传送者之间约定数据的类型,还要约定参数存放地。如果约定用通用寄存器传送参数,有可能会出现寄存器不够使用的情况。而约定用变量传送参数又要求在子程序和调用程序之外再写出变量定义,灵活性较差。,用堆栈传送参数就可以克服这些缺点。对于调用者来说,传送给子程序的数据可以按字型(如果不是字型,先要转换成字型)用PUSH指令压入堆栈中;对于子程序来说,如何准确地取到栈中数据就是关键性问题。下面的【例5-14】用一个实际例子说明子程序与调用程序如何利用堆栈传送参数的具体方法。,【例5-14】利用堆栈传送入口参数,编写子程序,把从堆栈中接收到的两个带符号整数中大的一个作为结果,出口参数放在AX中。,【解】;子程序名:max;功能:求两个带符号整数中大的一个;入口参数:调用前把两个带符号整数入栈;出口参数:AX;破坏寄存器:无。,maxPROCNEARPUSHBPMOVBP,SPMOVAX,WORDPTRBP+6CMPAX,WORDPTRBP+4JGElabMOVAX,WORDPTRBP+4Lab:POPBPRETmaxENDP,以堆栈传送入口参数,就是把需要传送的数据入栈,子程序再从栈中取出参数值。在调用子程序之前可以用PUSH指令把各参数依次压入栈中,然后以CALL指令调用子程序。对子程序而言,它面临的情况是,栈顶存放着返回的有效地址,这是不能更改的,否则无法正确返回,被传送的参数就压在这个返回地址的下面,子程序要在不破坏堆栈有效数据的前提下取得参数。,【解】PUSHSIPUSHDICALLmaxADDSP,4MOVDX,AX,不妨把【例5-14】和【例5-15】联系起来看一看栈的变化情况,了解参数传送的细节。先假设执行【例5-15】的程序段前SI=1234H,DI=9078H,SP=1000H,BP=08C5H,指令ADDSP,4所在的偏移地址是300H,则两个例子的具体执行过程如下:,(a)(b)(c)(d),图5-6在【例5-14】和【例5-15】中利用堆栈传送参数时栈的变化情况,8086指令系统中,还提供了一条带参返回指令RETn(前面5.2.2已作介绍)。利用这条带参返回指令,就可以把【例5-14】的子程序中的RET指令改为RET4,这样,【例5-15】的程序段中就不必再用ADDSP,4这一条指令了。,5.3.4子程序嵌套,主程序可以调用子程序,这个主程序就是调用程序,该子程序为被调用程序。在实际应用中,被调用程序还可以再去调用另一个子程序,这种情况称为子程序的嵌套。,子程序嵌套的层次(也称嵌套深度)没有限制,只要堆栈空间满足要求即可。图5-7(a)给出了一个具有两层子程序嵌套的示例,主程序MAIN调用子程序SUB1,SUB1又调用子程序SUB2。在返回时,必须是按顺序返回,即子程序SUB2执行完后返回到CALLSUB2的下一条指令处,继续执行子程序SUB1;待其执行完后,再返回到CALLSUB1的下一条指令处,继续执行主程序MAIN。,子程序嵌套功能的实现是借助堆栈来完成的。因为调用指令和返回指令都是通过堆栈操作来进行的,而且它们是按照“先进后出”或“后进先出”的原则工作的,这样就可保证依次取出返回地址。如图5-7(b)所示。,图5-7子程序嵌套示意图,(a)调用情况,(b)堆栈情况,对于子程序嵌套的程序设计,必须注意:(1)调用指令CALL和返回指令RET必须成对配合使用。CALL位于调用程序中,RET位于被调用子程序的出口处。(2)要注意寄存器内容的保护和恢复,避免各层子程序之间发生寄存器内容的冲突。,(3)如果程序中使用了堆栈,例如使用堆栈保护现场和恢复现场,那么,压栈操作和出栈操作必须成对进行。只有这样,才可保证每个子程序返回前SP正好指向返回地址。(4)若使用堆栈传递参数,则必须准确地安排堆栈操作,以保证程序能正确地返回。,【案例5-1】设从BUFFER开始存放若干个无符号字节型数据,编写程序,找出其中的最小值并以十六进制的形式输出。要求用子程序嵌套方法实现【案例分析】用子程序SEARCH来求最小数,并调用DISPCHAR进行输出。【案例求解如下】,DATASEGMENTBUFFERDB12,34,56,100,DB120,90,65COUNTEQU$-BUFFERDATAENDSCODESEGMENTASSUMECS:CODE,DS:DATASTARTPROCFAR,PUSHDSMOVAX,0PUSHAXMOVAX,DATAMOVDS,AXMOVCX,COUNT1LEASI,BUFFERCALLSEARCHRETSTARTENDP,SEARCHPROCNEARMOVBL,SISEAR1:INCSICMPBL,SIJBESEAR2MOVBL,SISEAR2:DECCXJNZSEAR1MOVDL,BLMOVCL,4SHRDL,CL,CALLDISPCHARMOVDL,BLANDDL,OFHCALLDISPCHARRETSEARCHENDPDISPCHARPROCNEARCMPDL,9JBEDISP1ADDDL,7,DISP1:ADDDL,30HMOVAH,2INT21HRETDISPCHARENDPCODEENDSENDSTART,说明:本程序采用了子程序嵌套,其中两次调用都是段内调用。主程序调用SEARCH子程序查找最小数,SEARCH子程序调用DISPCHAR子程序输出结果。,5.4子程序设计案例,【案例5-2】编写一个程序,其实现的功能是:把输入的一个字符串从指定的符号起切断,显示切断后的结果。要求把回车换行操作编写成子程序。,【案例分析】首先利用DOS10号功能调用从键盘读入一个符号串,接着调用cr回车换行子程序实现回车换行,再利用DOS1号功能读入一个符号,再次回车换行;然后从输入串的第一个符号起,依次取出每个字符显示到屏幕上,直至遇到某一字符与第二次输入的字符相同,或者把输入串显示完毕。【案例求解如下】,DsegSEGMENTbufDB80,81DUP(0)dsegENDSssegSEGMENTSTACKDW64DUP(0)SsegENDScsegSEGMENTASSUMECS:cseg,DS:dseg,SS:sseg,crPROCNEARMOVAH,2MOVDL,13INT21HMOVDL,10INT21HRETcrENDP,main:MOVAX,dsegMOVDS,AXLEADX,bufMOVAH,10INT21HCALLcrMOVAH,1INT21HMOVBL,AL,CALLcr;加如下4条指令MOVCL,buf+1MOVCH,0LEASI,buf+2lab2:MOVDL,SICMPDL,BLJZlab1MOVAH,2,INT21HINCSILOOPlab2lab1:MOVAH,4CHINT21HcsegENDSENDmain,【案例5-3】编写子程序write,把整型数据以十进制形式显示到屏幕上。【案例分析】参照高级语言中输出语句的功能,write:被显示的整数可以是无符号的,也可以是带符号的,但需要明确指出是哪一种情况;整数在计算机内部是字型数据,范围为:32768+65535,被输出的数据是带符号数时,负号“”必须输出,而正号“”总是省略;输出数据的最大位数是十进制的5位,当计算出5位中的某一位是0时,需要判断这个0是否应该输出,输出条件是前面已经输出过非0数字或者这个0是个位数。write子程序的流程图见图5-8。流程中的SI用于记载是否已输出过非0数字。,【案例求解】下面是按子程序格式编写的write子程序的清单,并附有简单注释。;子程序名:write;功能:在屏幕上输出整数值;入口参数:AX待输出的整数;CF为0表示输出无符号数,为1则输出带符号数;出口参数:无;破坏的寄存器:无,writePROCNEARPUSHAXPUSHBXPUSHCXPUSHDXPUSHSIPUSHDIMOVSI,0MOVDI,AX,JNCw1CMPAX,0JGEw1MOVDL,-MOVAH,2INT21HNEGDIw1:MOVBX,10000,MOVCX,5w2:MOVAX,DIMOVDX,0DIVBXMOVDI,DXCMPAL,0JNEw3CMPSI,0,JNEw3CMPCX,1JNEw4w3:MOVDL,ALADDDL,30HMOVAH,2INT21H,MOVSI,1w4:MOVAX,BXMOVDX,0MOVBX,10DIVBXMOVBX,AXLOOPw2,POPDIPOPSIPOPDXPOPCXPOPBXPOPAXRETwriteENDP,【案例5-4】编写子程序read,从键盘上读入一个整数。【案例分析】为了尽可能与高级语言中整数输入的情况一致,子程序不仅要能读入正确输入时的数据,还要能对不正确的输入做出适当的反应,因此设计上要注意几个问题:,首先是要用字符串输入方式(DOS的10号子功能),因为这种方式支持退格键修改功能,因而需要准备相应的输入缓冲区;出口参数需要两个,以CF的设置表示输入是否正确,当输入正确时把整数值放在AX中作为输入结果;要能够跳过若干个连续的空格符;要能够处理正负号。,【案例求解】;子程序名:read;功能:从键盘读入整数值。;入口参数:CF为0表示废弃多余符号,相当于READLN;为1则把多余符号留作下一次输入,相当于READ。;出口参数:CF为0表示正常读入,CF为1表示输入有错。;破坏寄存器:无。,readPRO
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年中医科学针灸治疗常见疾病操作技能考核答案及解析
- 2025年药物临床试验设计与实施考核答案及解析
- 2025年肝脏病诊疗新进展考核答案及解析
- 2025年急诊医学儿童发热处理常规演练答案及解析
- 2025年全身麻醉中监测指标解读考核答案及解析
- 2025年眼科手术操作规范性评估考试卷答案及解析
- 2025年神经内科疾病诊断与治疗算法模拟考试卷答案及解析
- 2025年外科学创伤救治技术综合测试卷答案及解析
- 2025年产科常见并发症防治专项考试答案及解析
- 2025年骨科手术后康复指导技能测评答案及解析
- 2025年江西南昌市西湖城市建设投资发展集团有限公司招聘笔试参考题库附带答案详解
- 职业教育产教融合型数字化教材开发研究
- 文学传播学概论课件
- 第3单元主题活动三《创意玩具DIY》(课件)三年级上册综合实践活动
- 《你的降落伞是什么颜色》读书笔记作品
- 商务英语词汇大全
- 电动机更换施工方案
- 《传统文化主题班会》课件
- 麻醉质量控制专家共识
- 体育-初中七年级田径大单元教学计划表及立定跳远教学设计、教案
- 反走私课件完整版本
评论
0/150
提交评论