汇编语言程序设计第6章-子程序课件

PPT
  • 阅读 66 次
  • 下载 0 次
  • 页数 113 页
  • 大小 636.000 KB
  • 2022-11-24 上传
  • 收藏
  • 违规举报
  • © 版权认领
下载文档40.00 元 加入VIP免费下载
此文档由【小橙橙】提供上传,收益归文档提供者,本网站只提供存储服务。若此文档侵犯了您的版权,欢迎进行违规举报版权认领
汇编语言程序设计第6章-子程序课件
可在后台配置第一页与第二页中间广告代码
汇编语言程序设计第6章-子程序课件
可在后台配置第二页与第三页中间广告代码
汇编语言程序设计第6章-子程序课件
可在后台配置第三页与第四页中间广告代码
汇编语言程序设计第6章-子程序课件
汇编语言程序设计第6章-子程序课件
还剩10页未读,继续阅读
【这是免费文档,您可以免费阅读】
/ 113
  • 收藏
  • 违规举报
  • © 版权认领
下载文档40.00 元 加入VIP免费下载
文本内容

【文档说明】汇编语言程序设计第6章-子程序课件.ppt,共(113)页,636.000 KB,由小橙橙上传

转载请保留链接:https://www.ichengzhen.cn/view-44775.html

以下为本文档部分文字说明:

第6章子程序6.1堆栈6.2子程序的基本格式和有关指令6.3应用子程序进行编程6.4整数输入与输出6.5子程序共享的方法*6.6递归本章要点习题六返回章目录6.1堆栈在汇编语言和机器语言中,堆栈在物理结构上是一段存放数据的连续的内存区域,以及一个称

为栈顶指针的专用存储单元。堆栈中只能存入16位的字型数据,存入数据的操作称为“进栈”或“压栈”,已存入的数据也可以取出,称为“出栈”或“弹出”,数据的存取操作由专用指令完成。从逻辑上说,堆栈是一种按“先进后出”原则进行操作的数据结构,栈顶指针用于指

出入栈操作和出栈操作的位置。6.1.1堆栈段图6.1是堆栈的物理结构示意图,图中标出的SS和SP是与堆栈密切相关的寄存器,SS存放堆栈所占用内存区域的段地址,SP所指向的位置称为栈顶。已入栈的数据存放区栈的空闲区SS:S

P栈顶指针一个程序如果要使用堆栈,必须先留出一片连续内存区域,方法是在程序中定义一个堆栈段。【基本格式】段名SEGMENTSTACKDWnDUP(?)段名ENDS【说明】(1)保留字STACK是堆栈段的专用符号,SEG

MENT后面的保留字STACK表明这个段专供堆栈使用。(2)段定义中用“DWnDUP(?)”说明堆栈所用内存区的大小为2n字节,其中n是一个常量。可根据程序需要,调节堆栈段的大小。因为堆栈只能存放字型数据,所以习惯上都是用DW伪指令来定义栈的大小。这不并是说用其它

伪指令不行。(3)按基本格式定义的栈是一个空栈,栈中没有存放有效数据。(4)为了使SS和SP在程序执行时取得正确的值,必须在源程序中写一条伪指令:ASSUMESS:堆栈段段名但不需要像DS和ES一样在程序中用指令进

行赋值。对SS和SP的赋值是由操作系统在把执行程序调入内存时由DOS本身完成的,DOS将把SS赋值为堆栈段的段地址,把SP赋值为2n。6.1.2进栈与出栈指令栈操作指令以它特有的方式存取数据,属于数据传递类指令,但又与MOV等指令有很

大的区别。6.1.2.1PUSH指令【指令格式】PUSHd【功能】先把SP的值减去2,然后把操作数d指明的字型数据放入以SS为段地址、SP为偏移地址所对应的内存单元中。【说明】(1)这是单操作数指令,操作数d可以是包括

段寄存器在内的任何字型寄存器,或者内存型寻址方式,但不能是立即寻址,当使用内存型寻址方式时可以使用段跨越。(2)PUSH指令的功能包括移动栈顶和存入数据两部分,两部分连续完成,密不可分。(3)操作数d进栈是以减2以后的SP的值作为偏移地址,但程序中不允许出现[SP]的写法。不要与基地址寄存器或

变址寄存器用作偏地址时的写法相混淆,也就是说,把PUSH指令理解成下面两条指令的组合是不正确的:SUBSP,2MOV[SP],d因为指令“MOV[SP],d”存在语法错误。(4)PUSH指令会导致栈顶指针的移动,如果用PUSH指令把很多数据进栈,使S

P不断减2,就有可能超出栈的有效范围。在一些高级语言中这种现象会导致堆栈溢出错误,但8088对此并不做任何检测和警告。因此要求编程人员自己注意控制堆栈的大小,估计可能进栈的数据量,以免由于栈溢出导致一些不可预测的后果。6.1.2.2POP指令【指令格式】POPd【功能】从S

S为段地址、SP为偏移地址对应的内存中取出一个字型数据,送到操作数d指定的位置,然后把SP的值加2。对操作数d的寻址方式要求与PUSH指令相同。堆栈通常用于临时保存数据。一般做法是先用PUSH指令把需要保

存的数据入栈,然后完成一定的指令序列,再用POP指令把原先保存的数据出栈。用堆栈保存数据的特点是不用定义变量,不必关心被保存的数据到底在栈的什么位置,只要保证出栈和进栈的对应关系即可。当CPU中的寄存器不够使用时经常用堆栈临时

保存数据。栈顶所指位置以上的部分是堆栈的空闲区,以下部分是已入栈的数据存放区(见图6.1),例6.1用来说明PUSH指令和POP指令对堆栈的影响。【例6.1】设AX=4F8AH,BX=307CH,SP=1

000H,分别逐条执行下列指令,用内存图的形式画出堆栈的变化情况,并分析程序段执行完后AX和BX寄存器的值。PUSHAXPUSHBXPOPAXPOPBX【解】堆栈变化见图6.2,程序段执行完后AX=307

CH,BX=4F8AH。XX0FFCXX0FFDXX0FFEXX0FFFYY1000SP→SP→XX0FFCXX0FFD8A0FFE4F0FFFYY10007C0FFC300FFD8A0FFE4F0FFFYY1000SP→(a)执行前(b)PUSHAX后(c)PUSHBX后

XX0FFCXX0FFCXX0FFDXX0FFDSP→8A0FFEXX0FFE4F0FFFXX0FFFYY1000SP→YY1000(d)POPBX后(e)POPAX后注:XX表示栈空闲区填充的无用数据

,YY表示栈中已存放的有效数据图6.2执行PUSH和POP指令对堆栈的影响6.1.2.3PUSHF和POPF指令【指令格式】PUSHF【功能】把SP的值减2,并把16位的标志寄存器送入SS:SP所指向的内存,即把标志寄

存器入栈。【指令格式】POPF【功能】把栈顶的一个16位的字型数据送入标志寄存器,并把SP的值加2。这两条指令相互配合可以设置标志寄存器中的任意一个标志位,一般的做法是:PUSHFPOPAX…;按标志位的分布情况和实际需要,修改AX

中的值PUSHAXPOPF6.2子程序的基本格式和有关指令6.2.1汇编语言子程序格式子程序是具有固定功能的程序段,并且有规定的格式。不同的计算机语言对子程序格式的规定不同,汇编语言的子程序基本格式如下:子程序名PROC类型指令序列子程序名ENDP格式中的首尾两行表示一个子程序的开始和

结束,都属于伪指令。“子程序名”是一个标识符,是编程者给子程序起的名字。子程序名同时还代表子程序第一条指令所在的逻辑地址,称为子程序的入口地址。“类型”只有NEAR和FAR两种,它将影响汇编程序对子程序调用指令CALL和返回指令RET的翻译方式。被夹在子程序起止伪指令之间的指令序列是完成子程序

固定功能的程序段,通常指令序列的最后一条指令是返回指令RET。6.2.2子程序相关指令6.2.2.1CALL指令【指令格式】CALL子程序名【功能】这是调用子程序的指令。根据被调用的子程序的类型不同,CALL指令的功能分为两种情

况:(1)如果被调用的子程序是NEAR类型,则先把当前指令指针IP的值入栈,这会使SP的值减2,然后把IP改成子程序的第1条指令的偏移地址。(2)如果被调用的子程序是FAR类型,则先把当前CS寄存器的值入栈,再把IP入栈,结果会使SP的值减4,

然后把CS和IP改为子程序第1条指令的逻辑地址。CALL也是一种跳转指令,与无条件跳转及条件跳转指令不同的是,CALL在跳转之前先预留了回来的方法,把IP的当前值或CS与IP的当前值入栈保存。从CS与IP的作用可以知道,它们存放的是正在执行

的指令的下一条指令的逻辑地址,现在这一地址被保存在堆栈中。于是回来的方法就显而易见了,只要从栈中取出逻辑地址值,送回IP或者CS与IP即可。这种返回操作就是由RET指令实现的。6.2.2.2RET指令【指

令格式】RET【功能】这是子程序返回指令,必须写在子程序的指令序列之中。根据所在的子程序的类型不同,RET指令的功能也分为两种情况:(1)如果RET所在子程序是NEAR类型,则从堆栈中出栈一个字(当然,SP会加2),送给IP。(2)如果

RET所在子程序是FAR类型,则先从堆栈中出栈一个字送到IP,再出栈一个字送到CS,栈顶指SP的值加4。CALL指令和RET指令都具有跳转的能力,与条件跳转及无条件跳转一样,都是通过修改IP或者CS与IP来实现的。不论跳转是由哪一条指令造成的,对

于只改变IP的跳转,跳转的目的地与跳转指令必然在同一个代码段内,这种跳转称为段内跳转。相应地,CALL指令功能的第一种情况称为段内调用,RET指令功能的第一种情况称为段内返回。另一种跳转是同时改变了CS和IP的值,这就允许跳转指令

与跳转目的地不在同一个段中,使得跳转的目的地可以在整个内存空间的任何位置,这一类跳转称为段间跳转。CALL指令功能的第二种情况称为段间调用,RET指令功能的第二种情况称为段间返回。6.2.3子程序的调用与返回在汇编语言程序中,子程序分为定义和

使用两部分。在较短的程序中,通常把子程序与其余指令写在同一个代码段内,一个代码段中可以定义多个子程序,并且都定义成NEAR类型。这样编写的代码段的基本结构如下:段名SEGMENT子程序1PROCNEAR子程序1ENDP

子程序2PROCNEAR子程序2ENDP子程序nPROCNEAR子程序nENDP入口标号:段名ENDS从入口标号起的程序段是主程序。RET指令必须出现在子程序中,而CALL指令可以出现在代码段的任何地方。主程序可以调用子程序,一个子程序可以调用另一个子程序,还可以调用它自

身,并且在书写次序上没有“先定义后调用”的限制。源程序中的指令段在经过汇编程序的翻译后,所有伪指令都不存在了。作为CALL指令的操作数,“子程序名”部分会翻译成子程序第一条指令的逻辑地址。当计算机在执行CALL指令时,CS和IP已经是下一条指令的逻辑地址。CALL指令具有保存当

前IP或者CS和IP并修改它们的值的能力,因此CALL执行完后,会按照新的CS及IP,转去执行子程序的第一条指令,并依次执行后续指令,完成子程序的功能,直至遇到RET指令。RET指令的执行效果是从栈中

取出由CALL保存的数据,恢复在执行CALL指令时的IP或者CS与IP值,从而回到CALL指令的下一行继续执行。【例6.2】分析下面的程序段的执行过程,以及在执行过程中堆栈及指令指针IP的变化情况,并假设在执行CALL指令前,SP的值是0FEH。subpPROCNEARINCAL;假设本指令所在

的偏移地址是1234HRETsubpENDPCALLsubpMOVAX,BX;假设本指令所在的偏移地址是5678H【解】(1)当计算机把CALLsubp对应的机器指令取到CPU中时,IP的值已经是CALL的下一行的MOV指令所在的偏移地址5678H,此时还未进栈,栈的情况如图6.

3(a)所示。(2)由于子程序subp是NEAR类型,按照CALL指令功能的第一种情况执行CALL指令,把IP的值入栈,并把IP的值改为subp子程序的入口地址1234H,此时堆栈的情况如图6.3(b)所示。(3)执行完CALL指令IP

的值已经变成1234H,CS没变,CPU按新的IP值,在CS段下取出一条指令,即INCAL指令。(4)执行INC指令时,CPU自动把IP变成INC的下一行指令的偏移地址,如此逐条执行子程序中的各指令,直至遇到subp子程序的最后一条指令RET。(5)执行

RET指令时,堆栈中的情况仍然是图6.3(b),因此执行RET就是取出栈顶所指的一个字,是5678H,并把它送给IP,执行完RET指令后堆栈的情况如图6.3(c)所示。(6)执行完RET指令后,IP的值已经变成5

678H,CPU按新的IP值,在CS段下取出一条指令,即MOVAX,BX指令,并继续执行下去。XX00FAXX00FAXX00FAXX00FBXX00FBXX00FBXX00FCSP→7800FCXX00FCXX00FD5600FDXX00FDSP→YY0

0FEYY00FESP→YY00FE(a)CALL指令执行前(b)CALL指令执行后(c)RET指令执行后图6.3例6.2的程序执行过程中堆栈的变化情况例6.2描述了段内调用与返回的过程,对于段间调用与返回,仅仅在CALL指令和RE

T指令的执行效果上不同,这个问题留给读者:把例6.2中的子程序类型改成FAR,执行过程中栈的变化情况又如何?例6.2中隐藏有一个非常严重的问题,就是如何保证执行完CALL指令后堆栈的情况与执行RET指令前堆栈的情况是相同的。这个问题确实存

在,并且是程序员不可回避的。因为完成子程序需要执行多条指令,这些指令中难免会有改变栈顶指针或者改动栈中数据的情况。但是,无论是汇编程序还是计算机硬件本身都对此无能为力,需要程序员自己在编制程序时非常小心。如果不能保证堆栈的情况

相同,执行到RET时,计算机仍然按照RET指令本身的功能正常处理,出栈一个字给IP或者连续出栈两个字分别给CS及IP,这时就不会回到调用指令CALL的下一行,而不知跳转到什么地方去了。【注意】为了避免出现这种情况,编制子程序时应该注意以下几点:(1)子程序

中的PUSH指令与POP指令数量应该相同,并且存在一一对应关系。(2)不要把SP用作MOV、ADD等指令的目的操作数,不要使用INCSP、DECSP等指令,不要使用类似指令改变SP的值。(3)不要使用PO

PSP指令,该指令会用出栈的一个字型数据修改SP,而不像正常的POP指令一样把SP加2。(4)如果子程序中再次用CALL指令去调用子程序,只要被调用的子程序正确,则不会导致出现上述问题。6.3应用子程序进行编程6.3.1子程序实例【例6

.3】分析下列程序,描述它的功能。dsegSEGMENTbufDB80,81DUP(0)dsegENDSssegSEGMENTSTACKDW64DUP(0)ssegENDScsegSEGMENTASSUMECS:cseg,DS:d

seg,SS:ssegcrPROCNEARMOVAH,2MOVDL,13INT21HMOVDL,10INT21HRETcrENDPmain:MOVAX,dsegMOVDS,AXLEADX,bufMOVAH,10INT21H;输入一个符号串CALLcrMOVAH,1INT21H;输入一个字符MO

VBL,AL;用BL保存读入的字符lab2:MOVDL,[SI]CMPDL,BLJZlab1;等于第2次输入的符号则转MOVAH,2INT21HINCSILOOPlab2lab1:MOVAH,4CHINT21Hcseg

ENDSENDmain6.3.2对子程序中用到的寄存器进行保护【例6.4】设子程序cr的定义如例6.3所示,比较下面两个程序段,分析各自执行完后寄存器AX中的值是多少。(a)MOVAX,102HMOVBX,304HADDAX,BX(b)MOVAX,102HMOVBX

,304HCALLcrADDAX,BX【解】程序段(a)中,先把AX赋值为102H,再把BX赋值为304H,然后用ADD指令把两数相加,和为406H,结果放在ADD指令的目的操作数AX中。程序段(b)的前两行与(a)完全相同,AX取值102H,BX取值304H,但在相加之前调用了子程序cr

。从例6.3中cr的具体实现方法可以知道,调用过程中寄存器AH的值被改为2,因为INT21H输出功能,使AL的值也被修改,变成0AH,并且这个值一直保持到调用结束,于是“CALLcr”指令调用子程序后,AX

的值不再是调用前的102H,而变成了20AH,当ADD指令进行两个寄存器相加时,结果是50EH,并放到目的操作数AX中。从例6.4可以看到,两个程序段仅仅相差一个子程序调用,而且子程序cr也只不过完成回车换行的操作,但两个程序段执行的结果却不一样,原

因就在于调用子程序前,寄存器AX中放了一个有用的数据102H,但子程序中对AX重新赋了值,破坏了原来的数据。子程序中修改寄存器的值会给程序编制带来很大的麻烦,就如例6.4(b)的情况,想要找出错误的原因是不太容易的。为此,做法之一是在调用前把有用的数据存

放到适当的地方保护起来,比如在例6.4(b)的CALL指令之前可以把AX的值先找一个寄存器(比如SI)临时存放,调用后再取回到AX中;另一个比较好的做法是在子程序中对所有使用到的寄存器进行保护,等到子程序的

功能完成后,再恢复这些寄存器的原值,最后以RET指令返回。按照这个原则,把例6.3的子程序cr改写成如下形式:crPROCNEARPUSHAXPUSHDXMOVAH,2MOVDL,13INT21HMOVDL,10INT21HPOPDXPOPAXRETcrENDP修

改后的子程序cr先把AX和DX的值入栈保护,完成回车换行操作后,再从栈中取出原来保存的数据恢复AX和DX的原值。用堆栈临时保存数据是子程序中普遍使用的一种方法。经过这样的修改,例6.4的两个程序段各自执行后,AX中的值就会是

一样的,调用子程序cr进行回车换行操作就不会影响程序的正常执行。【注意】入栈指令PUSH和出栈的POP指令必须一一对应。从栈操作的“先进后出”方式可以知道,入栈次序与出栈次序是相反的,所以PUSH指令序列

中操作数的次序与POP指令序列中操作数的次序相反,就如同上面的子程序cr中两条PUSH指令是先AX再DX,而两条POP则是先DX再AX。6.3.3带参数的子程序【例6.5】编写一个子程序,对一个无符号的

字型数组的各元素求和。在调用子程序之前,已把数组的段地址放在DS中,起始偏移地址放在寄存器SI中,数组元素个数(>0)放在CX中。要求子程序把计算结果以双字的形式存放,高位放在DX中,低位放在AX中。【解】sumPROCNEARPUSHBX;保护用到的寄存器BXXORAX,AXMOVDX,A

X;求和前先把存放结果的DX,AX清0MOVBX,AXs1:ADDAX,[BX+SI];把一个元素加到AX中ADCDX,0;若有进位,DX加1INCBXINCBX;BX加2,指向数组的下一元素LOOPs1POPBX;恢复寄存器BX的值RET

sumENDP子程序说明应该包含如下4个部分:(1)子程序的功能。用来指明该子程序完成什么样的操作。(2)入口参数。说明调用子程序前应该把什么样的数据放在什么地方。(3)出口参数。说明调用后从什么地方取得处理结果。(4)破坏的寄存

器。指出子程序中哪些寄存器没有被保护。6.3.4参数传递的方法1.通用寄存器传值2.通用寄存器传地址3.标志寄存器传递逻辑型数据【例6.6】编写一个子程序,以放在AX中的公元年份为入口参数,判断该年是否为闰年。另有一个应用程序,它已定义了一个字节型数组t,依次存放着12个月的每月

天数,其中2月份的天数是28。应用程序已经在DX中存放了年份值,利用前面编写的子程序,编写程序段调整数组t中2月份的天数。【解】子程序清单如下:;功能:判断一个年份是否为闰年;入口:AX=公元年份;出口:CF,1表示

是闰年,0表示非闰年;破坏寄存器:AXjudPROCNEARPUSHBXPUSHCXPUSHDXMOVCX,AX;临时保存年份值MOVDX,0MOVBX,4DIVBX;除以4,为预防溢出,用双字除以字CMPDX,0JNZlab1;不能整除4则不是闰年,转MOVAX,CX;取回年份值MOV

BX,100DIVBX;除以100CMPDX,0JNZlab2;不能整除100则是闰年,转MOVAX,CXMOVBX,400DIVBX;除以400CMPDX,0JZlab2lab1:CLC;把CF清0表示非闰年,设置出口参数JMPlab3la

b2:STC;把CF置1表示是闰年,设置出口参数lab3:POPDXPOPCXPOPBXRETjudENDP对于DX中存放的年份值,需要先放到AX中,才能调用子程序jud,然后以调用返回后的CF值决定是否把t数组

中表示2月份天数的[t+1]加1。程序段如下:MOVAX,DXCALLjudADCBYTEPTR[t+1],0;原值+0+CF4.用数据段中已定义的变量存放参数用数据段中定义的变量作为参数传递的载体也是一种常用方法。这种方法要求子程序与调用者之间约定好以

哪个变量或哪几个变量进行参数传递。具体做法是:对于用作入口参数的变量,调用者在调用子程序的CALL指令之前,先把变量赋以一定的值,然后以CALL指令转到子程序执行,子程序则取出该变量中的数据进行处理;对用作出口参数的变量,也有赋值与取值两个阶段,子程序进行数据处理后,把处

理结果放到约定好的变量中,然后以RET指令返回调用者,调用者可以从变量中取出处理结果使用。【例6.7】用变量传递参数,编写例6.6要求的子程序。【解】;功能:根据一个年份是否为闰年,设置该年2月份的天数;入口:DS段中的字型变量yea

r=公元年份;出口:DS段中的字节型变量day=该年2月份天数;破坏寄存器:无jud1PROCNEARPUSHAXPUSHBXPUSHCXPUSHDXMOVBYTEPTR[day],28MOVAX,[year]MOVDX,0MOVBX,4DIVBX;

除以4CMPDX,0JNZlab1;不能整除4则不是闰年,转MOVAX,[year];取回年份值MOVBX,100DIVBX;除以100CMPDX,0JNZlab2;不能整除100则是闰年,转MOVAX,[year]MOVBX,40

0DIVBX;除以400DIVBX;除以400CMPDX,0JZlab2lab2:INCBYTEPTR[day];是闰年,把天数加1,设置出口参数lab1:POPDXPOPCXPOPBXPOPAXRETJud1:ENDP5.用堆栈传递参数传递不仅要在传递者之间

约定数据的类型,还要约定参数存放地。如果约定用通用寄存器放参数,有可能会出现寄存器不够使用的情况。而约定用变量存放参数又要求在子程序和调用程序之外再写出变量定义,灵活性较差。用堆栈传递参数就可以克服这些缺点。对于调用者来说,传递给子程序的数据可以按字型(如果不是字型,先要转换成字型)用P

USH指令压入堆栈中;对于子程序来说,如何准确地取到栈中数据就是关键性问题。下面的例6.8用一个实际例子说明在子程序中取得参数值的具体方法。【例6.8】用堆栈传递入口参数,编写子程序,把接收的两个带符号整数中大的一个作为结果,出口参数放在AX中。【解】;功能:求两个带符号整数中大

的一个;入口参数:调用前把两个带符号整数入栈;出口参数:AX;破坏寄存器:无_maxPROCNEARPUSHBP;暂时保存寄存器BP的值MOVBP,SPMOVAX,WORDPTR[BP+6];取第1个参数到AXCMPAX,WORDPTR[

BP+4];与第2个参数比较JGElabMOVAX,WORDPTR[BP+4];取第2个参数到AXlab:POPBP;恢复寄存器BP的原值RET_maxENDP6.3.5子程序的嵌套调用一个子程序往往有其固定的功能,子程序

中的指令序列是实现这种功能的具体方法和步骤,这个指令序列中可能、也允许再出现CALL指令。这种由一个子程序中的CALL指令去调用另一个子程序的形式就是子程序的嵌套调用。汇编语言对于子程序嵌套调用几乎没有什么限制,主程序可以调用子程序,子

程序可以再调用其它子程序,也可以调用其自身(即后面所说的递归)。可以说,汇编语言源程序中,任何可以写指令的地方都可以写一条CALL指令,这一点与Pascal语言有很大的不同。在Pascal语言中,每个子程序(过程或函数)都有其归属问题,主程序下辖若干个子程序,一个子程序可以再管辖它的若干子程

序。整个归属关系构成树形结构,主程序是树根,每个子程序是树中的一个结点,没有下属子程序的结点是树叶。对于树形结构中的任何一个子程序(记作SUBP),如果把从根结点到这个子程序所经过的各个子程序称为一条路径,则任何子程序只有唯一的路径,这条路径上的任何结点都是SUBP的祖先。在Pas

cal中规定,SUBP可以调用:(1)其直接管辖的子程序,即它的子结点。(2)它自身。(3)它的任何祖先,主程序除外。(4)它的祖先所管辖的直接子结点。另外,任何调用都必须遵守“先定义后调用”的规则。6.4整数输入与输出对于高级语言来说,整数的输入输出是标准的输入输出命令(语句)必

备的功能。把数值型数据按正确写法写在输出命令中,就可以在屏幕上得到输出结果;在输入语句中写上正确的数值型变量,就可以把键盘上按键情况变成数值放到指定变量中。但是在汇编语言中,没有这类指令或功能供直接调用,而只有输入字符型数据(即ASCII值)的方法,所以需要程序员自己编写整数输

入输出的程序段,不过这样的程序段功能固定、使用频繁,适合于编写成子程序的形式,在各个需要它的程序中共享。【例6.10】编写子程序write,把整型数据以十进制形式显示到屏幕上。【分析】参照高级语言中输出语句的功能,write子程序应具备这样一些特点:被显

示的整数可以是无符号的,也可以是带符号的,但需要明确指出是哪一种情况;整数在计算机内部是字型数据,范围为-32768~+65535;被输出的数据是带符号数时,负号“-”必须输出,而正号“+”总是省略;输出数据的最大位数是十进制的5位,当计算出5位中的某一位是0时,需要判断这个0是否应该输

出,输出条件是前面已经输出过非0数字或者这个0是个位数。write子程序的流程图见图6.5。流程中的SI用于记载是否已输出过非0数字。【解】下面是按子程序格式编写的write的清单,并附有简单注释。;功能:在屏幕上输出整数值;入口:AX=待输出的整数;CF=为0表示输出无符号数,为1则输出带符号

数;出口:无;破坏寄存器:无writePROCNEARPUSHAXPUSHBXPUSHCXPUSHDXPUSHSIPUSHDIMOVSI,0;SI清0表示还没有输出过非0数字MOVDI,AX;保存待输出的数值到DI中JNCw1;作为无符号数输出转

CMPAX,0JGEw1;AX是正数转MOVDL,'-'MOVAH,2INT21H;输出负号NEGDI;取绝对值放在DI中w1:MOVBX,10000;第一次的被除数MOVCX,5;重复次数w2:MOVAX,DI;取回待输出

数值MOVDX,0;被除数高位清0DIVBX;做双字除以字的除法MOVDI,DX;余数保存在DI中CMPAL,0JNEw3;商非0转CMPSI,0;商是0,判断前面是否输出过数字JNEw3;前面已输出过数字,则当前的0应该输出,转CMPCX,1;判断是否是个位JNEw4;不是个位则不输出当前的0

,转w3:MOVDL,ALADDDL,30HMOVAH,2INT21H;输出当前这一位数字MOVSI,1;用SI记载已输出过数字w4:MOVAX,BXMOVDX,0MOVBX,10DIVBXMOVBX,AX;bx/10=>bx,

计算下一次的除数LOOPw2POPDIPOPSIPOPDXPOPCXPOPBXPOPAXRETwriteENDP相对于整数输出而言,整数的输入问题就更加复杂,因为它不仅是提供给操作人员输入整数的方法,而且要处理操作员可能的操作错误。不妨考察一下高级语言中整数输入语句对键盘输入

的要求:(1)等待键盘输入,直到操作员按回车键,按回车键前如果发现输入有误,可以用退格键删去错误部分并重新输入。(2)输入串可以是一串数字。(3)输入串可以是一个正(或负)号,再紧接着一串数字。(4)输入串可以

是若干个空格之后,再出现(2)或(3)的情况。(5)当输入串是(2)至(4)的某一种情况,但后面有多余符号时,则当前一次输入只到正确的输入串为止,后续多余的符号留作下一次输入的符号串,也可以废弃多余的符号,如同Pascal语言中READLN的处理方式。(6)当输入串是数字但超出正确

范围时,多数高级语言的处理方法是忽略掉超范围部分,即整数的内部表示共16位,对超过16位的部分自动忽略。(7)当输入串不正确时,不同的高级语言处理方法不同,但一个总的原则是要指出输入有错误。【例6.11】编写子程序read

,从键盘上读入一个整数。【分析】为了尽可能与高级语言中整数输入的情况一致,子程序不仅要能读入正确输入时的数据,还要能对不正确的输入做出适当的反应,因此设计上要注意几个问题:首先是要用字符串输入方式(DOS的10

号子功能),因为这种方式支持退格键修改功能,因而需要准备相应的输入缓冲区;出口参数需要两个,以CF的设置表示输入是否正确,当输入正确时把整数值放在AX中作为输入结果;要能够跳过若干个连续的空格符;要能够处理正负号。【解】;功能:从键盘读入整数值;入口:CF=为0表

示废弃多余符号,相当于READLN;;为1则把多余符号留作下一次输入,相当于READ;出口:CF=0表示正常读入,1表示输入有错;破坏寄存器:无readPROCNEARPUSHBXPUSHCXPUSHDXPUSHSIPUSHDS;以上为寄存器保护PUSHFPU

SHCSPOPDS;令DS取CS的值rd1:MOVBX,CS:[point];取上次输入后已读取到输入串的位置rd2:INCBXCMPCS:[bufin+BX+1],''JErd2;跳过空格CMPCS:[bufin+BX+1],13JNZrd4;不是回车键,转读

入数值处理rd3:LEADX,CS:[bufin]MOVAH,10INT21H;遇回车键要求再次输入MOVAH,2MOVDL,10INT21H;换行MOVCS:[point],0JMPrd1;对新的输入再转

去跳过前导空格rd4:MOVSI,BXDECSI;令SI指向输入串的第一个有效字符MOVAX,0MOVBX,10MOVCX,0rd5:CMPCS:[bufin+SI+2],'+'JNZrd6;不是正号转CMPC

L,1JErd10;已读到正确数值后,遇正号转CMPCL,0JErd8;正号是第一个有效字符转STC;输入有错JMPrd13rd6:CMPCS:[bufin+SI+2],'-'JNZrd9CMPCL,1;已读到正确数值后,遇负号转JErd10CMPCL,0JErd7;负号是

第一个有效字符转STC;输入有错JMPrd13rd7:MOVCH,1;记下读入的是负数rd8:MOVCL,2;记下已读入正/负号INCSI;指向下一字符JMPrd5rd9:CMPCS:[bufin+SI+2],'0'

JBrd10;不是数字转CMPCS:[bufin+SI+2],'9'JArd10;不是数字转MULBX;已读入的数值×10MOVDL,CS:[bufin+SI+2]SUBDL,30hMOVDH,0ADDAX,DX;乘以10后加上个位数字MOV

CL,1;记下已读入正确数值INCSI;指向下一字符JMPrd5rd10:CMPCL,1JZrd11;已读入正确数值转STC;输入有错JMPrd13rd11:CMPCH,1JNZrd12;已读入的数是正数转NEGAX;处理负数rd12:CLC;置正确读入标志rd

13:MOVCS:[point],SI;记下读完后的位置,供下次读入使用POPBX;取回进入子程序时入栈保护的PSW,送BXPUSHF;当前的PSW入栈保存TESTBX,1;判断进入子程序时的CF值JNZrd14;CF为1,保留多余符号转MOVCS:[bu

fin+2],13MOVCS:[point],0rd14:POPF;取回入栈保存的PSWPOPDS;以下恢复各寄存器值并返回POPSIPOPDXPOPCXPOPBXRETbufinDB128,0,13,127dup(0);键盘输入缓冲区pointDW0;用于记载下

一次的读取位置readENDP6.5子程序共享的方法6.5.1复制子程序的源代码整理出每一个子程序的源代码清单,按汇编语言写注释的规定写上必备的说明,单独构成一个程序文件(文本文件)。把很多个这样的程序文件集中放在一个子

目录(文件夹)中,构成一个子程序库。以后如果某个程序中需要一个子程序,只要从子程序库中以文件复制的方法,把所需要的子程序的清单复制到需要它的源程序中即可。为每个子程序建立一个文件,在管理上和使用上有如下特点:(1)需要人工管理。人工地从一些源程序中截取出子程序清单,建立单独的

文件;如果在特定目录中的子程序文件遇到同名问题,就需要人工解决。(2)以复制方式共享。一个程序中需要用到某个子程序时,可以从特定目录中挑选出需要的子程序文件,复制其中的程序清单到需要它的源程序中。(3)子程序库易于维护。如果需要更新某个子程序文件,一种方法是把

更新版本的子程序以同名文件的形式复制过来,另一种方法是直接用编辑器对原有的文件进行修改。无论哪种方法,实现起来都很简便易行。(4)子程序清单需要多次汇编。由于子程序清单是以文本文件的形式存在,使用时是复制到源程序中,因此会随着源程序的汇编操作一道进行汇编。(5)可能存在标识符冲突。子程序中,除了子

程序名之外有可能还会用到一些标识符,如子程序内需要使用的变量、标号等。当子程序清单复制到某个源程序之后,子程序内的标识符有可能与程序的其余部分的标识符同名,这种标识符的重复定义是不符合汇编语言语法规定的,需要修改。6.5.2INCLUDE伪指令【伪指令格式】IN

CLUDE文件名【功能】告诉汇编程序,把“文件名”所指出的文本文件的内容调到INCLUDE伪指令所在处,对拼装后的源程序进行汇编。【例6.12】设存放在磁盘上有3个文件cr.asm、write.asm和read.asm,分别存放的是例6.1

0的子程序write和例6.11的子程序read,以及回车换行子程序cr的程序清单。使用INCLUDE伪指令编写完整程序,从键盘上读入两个整数,求和。【解】dataSEGMENTbufDB13,10,'Inputerror.',13,10,'$'dataENDScodeSEGMENTASSUMED

S:data,CS:codemain:MOVDX,dataMOVDS,DXCLCCALLread;读入第1个整数JCerr;输入有错转MOVCX,AXCALLread;读入第2个整数JCerr;输入有错转MOVBX,AXCALLcrMOVAX

,BXADDAX,CX;计算两个整数的和STCCALLwrite;输出两个整数的和,带符号JMPlaberr:LEADX,bufMOVAH,9INT21H;输出提示信息Inputerrorlab:MOVAH,4C

HINT21HINCLUDEcr.asm;此处调入文件cr.asmINCLUDEwrite.asm;此处调入文件write.asmINCLUDEread.asm;此处调入文件read.asmcodeENDSENDm

ain6.5.3库文件(.LIB)子程序共享还有一种方法是建立子程序的库文件(.LIB)。在MicroSoft提供的汇编语言工作环境中,有一个专门管理子程序库文件的程序LIB.EXE,它的主要功能包括建立新的库文件、向库文件中放入新的子程序和从中取出子程序,以及更新其中的子程序。使用LIB软

件之前,必须先把子程序写入一个段中,并且用PUBLIC伪指令说明该子程序可供外部调用,然后把子程序汇编成目标程序。下面以回车换行子程序为例,具体说明操作过程。(1)编辑子程序文件cr.asm,在子程序清单上加上程序的基本格式及有关伪指令,构成一个完整的“模块”,成为

如下的形式:PUBLICcr;名字cr可以供其它程序调用ASSUMECS:subpsubpSEGMENT;写成一个段的形式crPROCFAR;子程序与调用者不在同一个段中,用FAR类型PUSHAXPUSHDXMOVAH,2MOVDL,13INT21HM

OVDL,10INT21HPOPDXPOPAXRETcrENDPsubpENDSEND;是子模块,不需要入口地址程序清单中的PUBLIC是伪指令。当一个大型程序由若干个源程序组合而成时,用PUBLIC说明一个程序中的标识符是公用的,其它源程序中可以使用该标识符。程序结束伪指令END的后面没有入口地

址,这是因为这个程序文件不是完整程序,只是一个模块,需要与其它模块连接在一起。(2)汇编。在DOS的命令行状态下,用下面的命令把cr.asm汇编成cr.obj。MASMCR;(3)把目标文件放入库文件中。完成以上两步操作之后,盘上就有了包含子程序

cr的目标文件cr.obj。下一步工作是建立一个子程序的库文件,把子程序cr放入库文件中。这时需要LIB软件的帮助。使用库管理程序LIB的一般格式是:LIB库文件名x目标文件名其中符号x表示操作命令,有以下几种情况:+把目标文件放入库文件-从库文件中删除目标文件

-+用新的目标文件替换库文件中已有的同名目标文件*从库文件中取出目标文件,在盘上建立一个obj文件,但不删除库文件中的内容-*从库文件中取出目标文件,在盘上建立一个obj文件,并且删除库文件中的相应内容格式中的“库文件名”是磁盘上已存在的一个.LIB文件。在新建一个库文件(.LIB)

时,盘上并不存在这样的文件,这时LIB软件会按库文件名的指定,新建该库文件。继续上面的例子,要新建一个库文件MYSUB.LIB,并把已汇编好的目标文件cr.obj放入其中,使用的命令是:LIBMYSUB+CR(4)使用库文件中的子程序。正确完成前面的操作后,

在盘上就有了一个名为MYSUB.LIB的库文件,其中含有FAR类型的子程序cr。那么又如何在编程时使用子程序cr呢?下面用一个例子说明具体方法。【例6.13】在程序中使用子程序库MYSUB.LIB中的子程序cr完成回车换行操作。EXTRNcr

:FAR;说明标识符cr是一个外部定义的FAR型子程序codeSEGMENTASSUMECS:codemain:MOVAH,2MOVDL,'1'INT21HCALLcr;调用外部子程序crMOVDL,'2‘MOVAH,2INT21HMOVAX,4C00HINT21HcodeENDSEN

Dmain不妨设把上述程序输入计算机并以T.ASM为文件名存盘,经过MASM的翻译处理,得到一个目标文件T.OBJ。这个文件与子程序库文件MYSUB.LIB拼装在一起才能构成一个完整程序。在使用LINK对目标文件进行连接时,必须指明需要连接的库文件名,即在LINK软件执

行到询问库文件名(Libraries)时,输入MYSUB.LIB。操作过程如下:C:\MASM>LINK↙ObjectModules[.OBJ]:T↙RunFile[T.EXE]:↙ListFile[NUL.MAP]:↙Libraries[.LIB]:MYSUB

↙其中带下划线的是LINK的提示部分,后面是操作者的输入,符号“↙”表示回车。经过上述操作,可以得到一个由两个模块拼装起来的可执行文件T.EXE。共享子程序有前面说到的三种常用方法,其中使用子程序库(.LIB)是三种方法中最好的一种

,尤其是在编写一系列相互关联的程序时,子程序库更能显示出它的优势。*6.6递归使用递归法进行求解的问题需要具备以下几个条件:(1)原始问题可以分解成几个小问题,每个小问题的答案组合在一起可以得到原问题的解。(2)分解出的每个小问题,要么可以简单解得,要么与原始问题是同一种类型,且

比原问题稍简单。(3)在经过有限次分解后,可以得到问题的最简单形式,而且最简形式的问题是可解的。递归与循环有一定的相似之处,都是同类问题的相似重复,重复操作在达到一定的条件后可以终止。重复正是计算机的主要特长。递归问题的分解次

数称为递归的深度。要想让计算机完成递归问题的求解必须解决好这样几个问题:首先是用计算机语言描述出如何把问题分解,并且要让计算机记住分解出的是几个什么样的小问题;其次要告诉计算机对分解出的每个问题如何处理,充分简单的问题如何求解,同型问题如何

重复;再就是要告诉计算机,当分解出的问题满足什么条件时不继续分解;最后还要能够让计算机把各个小问题的答案逐次组合成最终解。要让计算机记住当前已把问题解决到何种程度,就必须有相应的存储机制和存储空间。存储空间的多少往往在编写程序时并不知道,而是与需

要求解的问题本身的大小有关;存储机制必须能够把较早期分解出的情况在组装答案时较晚取出,也就是“先进后出”。堆栈是各种存储结构中比较符合条件的一种,“先进后出”是栈操作的基本特征,而栈的总容量虽然必须在程序设计时确定,但可以根据计算机的性能设定为一个比

较大的值。无论是高级语言还是汇编语言,递归都是用带参数的子程序来实现的,其中的参数用来描述当前问题的复杂程度。递归则表现为在子程序的内部再调用它自身。递归子程序的基本模式是:若参数满足问题的最简条件,则直接求解答案,

作为本次子程序调用的结果否则求解分解出的小问题中可直接求解者;对小问题中的同型者,以较简单的参数调用自身;把各个小问题的答案拼装,作为本次调用结果。下面是Pascal语言的一个递归子程序,用于求n!。FUNCTIONfact(n:INTEGER):INTEGER;BEGINIFn<=1

THENfact:=1ELSEfact:=n*fact(n-1);END;【例6.14】用汇编语言编写递归子程序,实现PASCAL子程序fact的功能。【解】;==================fact=================;功能:计算n!;入口:调用前把整数n入栈;

出口:AX=n!的计算结果;破坏寄存器:无;==========================================factPROCNEARPUSHBPMOVBP,SPPUSHBXPUSHDXMOVBX,[B

P+4]CMPBX,1JGf1;参数n>1转MOVAX,1;最简情况,把返回结果放到AX中JMPf2f1:MOVDX,BXDECDX;计算n-1PUSHDX;把n-1入栈,准备递归调用CALLfact;递归调用,结果从AX带回POPDX;丢弃调用前入栈的参数M

ULBX;AX*BX,在AX中的积作为拼装结果f2:POPDXPOPBXPOPBPRETfactENDP【例6.15】设例6.10和例6.11编写的子程序read和write已建立在子程序库中。在例6.14的子程序fact的基

础上,编写完整程序,从键盘读入一个正整数,显示出它的阶乘值。【解】EXTRNread:FAR,write:FAR,cr:FARcodeSEGMENT;(2)ASSUMECS:CODE,SS:s;(3)factPROCNEAR;(4)PUSHBP;(5)0000MOVBP,S

P;(6)0001PUSHBX;(7)0003PUSHDX;(8)0004MOVBX,[BP+4];(9)0005CMPBX,1;(10)0008JGf1;(11)000BMOVAX,1;(12)000DJMPf2;(13)00

10f1:MOVDX,BX;(14)0013DECDX;(15)0015PUSHDX;(16)0016CALLfact;(17)0017POPDX;(18)001AMULBX;(19)001Bf2:POPDX;(20)001DPOP

BX;(21)001EPOPBP;(22)001FRET;(23)0020factENDP;(24)main:CALLread;(25)0021读入一个整数CALLcr;(26)0026回车换行MOVBX,AX;(27)002B保留读入的整数值nCALLwrite;(28)002D

输出nMOVAH,2;(29)0032MOVDL,'!';(30)0034INT21H;(31)0036输出!MOVDL,'=‘;(32)0038INT21H;(33)003A输出=PUSHBX;(34)003CCALLfact;(35)

003D计算n!POPBX;(36)0040CLC;(37)0041CALLwrite;(38)0042输出n!MOVAX,4C00H;(39)0047INT21H;(40)004AcodeENDS;(41)sSEGMENTSTACK;(42)堆栈段DW

512DUP(0);(43)sENDS;(44)ENDmain;(45)尽管源程序的第一条有效指令在第5行,但第45行的END伪指令后面的标号指出程序的入口在第25行的CALL指令,操作系统把执行文件调入内存时,自动把CS置为code段的段地址,把IP赋值为第25行的偏移地址0021

H,并把SS置为s段的段地址,SP置为0400H表示空栈。程序首先调用read子程序读入一个整数,不妨设输入的是整数3,在出口参数规定的AX寄存器中;然后在第28到第33行输出“3!=”字样,执行完第33行后堆栈情况

如图6.7(a)所示;第34行入栈的BX是3,这是在第27行赋的值,入栈后栈的情况见图6.7(b);当程序执行到第35行的CALL指令时,IP已是第36行的偏移地址0040H,而CALL所调用的子程序fact是

NEAR类型,所以执行结果IP的值被压入堆栈,并把IP修改为fact的入口地址0000,此时堆栈的情况见图6.7(c)。按照IP的新值,计算机转入fact子程序。执行第5行,把BP的当前值0000压入堆栈后,栈中情

况见图6.7(d);程序第6行把SP的当前值03FAH送到BP中;然后是两条入栈指令,把BX和DX的当前值入栈;第9行以BP值03FAH加4后的03FEH作为偏移地址,在缺省段SS所指的堆栈段中取出一个字型数据,从图6.7(d)中可

看到取出的值是3,即第34行压入的参数值;由于3>1,将由第11行的JG指令转到第14行,接下来的三条指令把BX的值减1,得到2,并压入堆栈中;第17行的CALL指令又要调用NEAR型子程序fact,先把IP的当前值001AH入栈,再把IP改为fact的入口地址0000

,此时栈中情况见图6.7(e);根据IP的新值再转到第5行,执行PUSH指令,把BP的当前值03FAH入栈;再把BP赋值为当前SP的值03F0H,经两次压栈后,把BP+4的值03F4H作为偏移地址,到SS堆栈段中取

出一个字型数据,由图6.7(e)可知取出的是2,送到BX中;因为2>1,再转到第14行,把减1后的参数值1压入栈中;再次调用fact子程序,把IP的当前值001AH压栈后,IP改为0000,转到第5行继续执行,此时栈中情况见图6.7(f);BP值入栈后又重新赋

值为SP的当前值03E6H,两条压栈指令之后,从堆栈段的偏移地址03E6H+4=03EAH处取出参数值1,这一次第11行的JG指令不能实现转向,接着执行第12行,把AX赋值为1,这是已递归调用把问题分解到最简情况了,此时栈的情况见图6.7(g),递归深度为3。0

3E203E403E603E803EA03EC03EE03F003F203F403F603F803FA03FC03EE040000030040003000000400003001A0002003D0003000000400003001A000

10002000303FA001A0002003D00030000004000030001000203F0001A00010002000303FA001A0002003D0003000000400003(a)(b)(c)(d)(e)(f

)(g)处理完最简情况后,已在AX中准备好最深层调用的结果,即出口参数值,再由第13行转到第20行,连续三条出栈指令,从图6.7(g)所示的栈中依次取出三个字0001、0002和03F0H,分别给DX、BX及BP,此时栈的情况又回到图6.7(f)。现在,递

归调用开始从最深一层逐步返回,并在每一层返回过程中要完成本层答案的组装。在求阶乘问题中组装就是做本层参数与深一层调用结果的乘法操作。按照NEAR型子程序的RET指令的功能,从当前栈(图6.7(f))中出栈一个字给IP,使IP变为001

AH,转到第18行,回到深度2的那一层。废弃栈顶存放的过时参数后,第19行完成乘法操作,把AX的值1与BX中的2相乘,结果刚好在AX中,准备好本层的出口参数,再连续出栈三个字0002、0003和03FAH

,分别给DX、BX和BP,栈的情况再次回到图6.7(e),栈顶数据001AH刚好是本层返回目的地。RET指令完成返回操作,回到深度1的那一层,再次废弃栈顶过时参数,把深度2层带回的AX值2与当前BX的值3相乘,结

果还放在AX中作为本层出口参数,连续出栈三个字003DH、0003、0000,分别给DX、BX和BP,使栈的变化经过图6.7(d)的情况到达图6.7(c)。此时栈顶数据是0040H,深度1层的RET指令使该数据出栈并送到IP中,使程序转到第36行继续执行。至此递归调用已结束,回到了主程序

,只是栈顶还存放着一个过时的参数值,由主程序的第36行废弃该值。以递归调用完成3!的计算之后,fact的出口参数值6在AX中,刚好作为write子程序的入口参数,另一入口参数是在第37行设置CF为0,表示按无符号数输出,然后在

第38行调用write在屏幕上显示数值6;所有功能完成后,程序把控制权交回给DOS。屏幕上完整的显示是:3!=6本章要点子程序是程序设计的重要方法,除了子程序本身的格式以外,还涉及到如何调用、如何返回

、如何传递参数等问题。堆栈是一种先进后出的数据结构,它能够存放字型数据。PUSH和POP是栈操作的专用指令,分别用于入栈和出栈操作。堆栈通常用于临时保存数据。子程序是具有固定的功能、结构完整的程序段,写在

指令段中,由代码段中的CALL指令调用,完成固有的操作后以RET指令返回。CALL与RET都涉及入栈出栈操作,并且根据子程序的类型是NEAR还是FAR,汇编程序将把CALL和RET翻译成不同的机器指令。子程序的参数传递有寄存器传值、寄存器传地址、标志寄存器传递逻辑型

数据、用数据段中已定义的变量存放参数和用堆栈传递共5种方式,每种方式各有其特点,往往配合使用。整数的输入输出是汇编语言中比较难以处理的问题,书中列出的read和write子程序可以完成整数输入输出操作,并与高级语言中的相应标准过程有近似的功能。模块化程序设计是编写大型软件时的常用方法。汇

编语言的一个模块由若干个段和一个结束伪指令END构成,并且只有主模块的END伪指令后面需要入口地址。各模块可以各自用汇编程序进行翻译,得到各自的目标文件,再用LINK软件连接成一个完整的程序。各个模块间相互通讯时,要用PUBLIC和EXTR

N伪指令说明标识符是公共的或外部的。除了建立子程序库文件(.LIB)之外,还有其它方法可以实现多个程序段间的拼接。汇编语言的子程序可以进行递归调用,递归子程序在编写上有其特殊性,需要把问题进行分解,找出最简情况,对最

简情况下的问题直接求解,非最简情况则递归调用进一步分解。分解后的各个子问题最终还要拼装在一起,构成原问题的解。习题六6.1利用例6.3中的子程序cr,画图说明下面的程序段在执行过程中堆栈的变化情况:PUSHAXPUSHD

XCALLcrPOPDXPOPAX6.2说明子程序定义时,类型NEAR和类型FAR有什么不同。6.3说明如何把整数输入输出子程序read和write添加到本章6.5.3节中已建立的子程序库MYSUB.LIB中。6.4利用上一题的子程序库M

YSUB.LIB中已加入的子程序read和write,编写完整程序,对输入的两个整数比较大小,输出其中大的一个。6.5编写一个排序子程序sort,以DS、SI和CX作为入口参数,把DS:SI为起始地址的一个带符号的字型

数组进行由小到大排序,参数CX中存放的是数组中元素个数,并把该子程序添加到MYSUB.LIB子程序库中。6.6编写一个子程序,判断一个无符号数是不是素数。入口参数和出口参数自行设计。*6.7编写递归子程序,求两个正整数

的最大公约数。*6.8编写递归子程序,求下面的数列的第n项:a0=a1=1,ai=ai-2+ai-1,(i=2,3,…)6.9为下面的问题设计子程序的入口参数和出口参数:(1)求比x大的下一个素数。(2)求3个带

符号整数的平均值(不计余数)。(3)判断一个字符串是否包含一个特定的字符。(4)比较两个英文单词的大小(按字典顺序)。(5)把一个数插入到一个有序数组中,并保护有序。(6)取出一个数组中的所有偶数,放到另一个数组中。

小橙橙
小橙橙
文档分享,欢迎浏览!
  • 文档 25747
  • 被下载 7
  • 被收藏 0
相关资源
广告代码123
若发现您的权益受到侵害,请立即联系客服,我们会尽快为您处理。侵权客服QQ:395972555 (支持时间:9:00-21:00) 公众号
Powered by 太赞文库
×
确认删除?