汇编语言程序设计第6章-子程序课件.ppt
- 【下载声明】
1. 本站全部试题类文档,若标题没写含答案,则无答案;标题注明含答案的文档,主观题也可能无答案。请谨慎下单,一旦售出,不予退换。
2. 本站全部PPT文档均不含视频和音频,PPT中出现的音频或视频标识(或文字)仅表示流程,实际无音频或视频文件。请谨慎下单,一旦售出,不予退换。
3. 本页资料《汇编语言程序设计第6章-子程序课件.ppt》由用户(晟晟文业)主动上传,其收益全归该用户。163文库仅提供信息存储空间,仅对该用户上传内容的表现方式做保护处理,对上传内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知163文库(点击联系客服),我们立即给予删除!
4. 请根据预览情况,自愿下载本文。本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
5. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007及以上版本和PDF阅读器,压缩文件请下载最新的WinRAR软件解压。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 汇编语言 程序设计 子程序 课件
- 资源描述:
-
1、 第第6章章 子程序子程序 6.1 堆栈堆栈 6.2 子程序的基本格式和有关指令子程序的基本格式和有关指令 6.3 应用子程序进行编程应用子程序进行编程 6.4 整数输入与输出整数输入与输出 6.5 子程序共享的方法子程序共享的方法 *6.6 递归递归 本章要点本章要点 习题六习题六 返回章目录6.1 堆栈堆栈 在汇编语言和机器语言中,堆栈在物理结构上是一段存放数据的连续的内存区域,以及一个称为栈顶指针的专用存储单元。堆栈中只能存入16位的字型数据,存入数据的操作称为“进栈”或“压栈”,已存入的数据也可以取出,称为“出栈”或“弹出”,数据的存取操作由专用指令完成。从逻辑上说,堆栈是一种按“先进
2、后出”原则进行操作的数据结构,栈顶指针用于指出入栈操作和出栈操作的位置。6.1.1 堆栈段堆栈段 图6.1是堆栈的物理结构示意图,图中标出的SS和SP是与堆栈密切相关的寄存器,SS存放堆栈所占用内存区域的段地址,SP所指向的位置称为栈顶。已入栈的数据存放区 栈的空闲区 SS:SP栈顶指针 一个程序如果要使用堆栈,必须先留出一片连续内存区域,方法是在程序中定义一个堆栈段。【基本格式】段名 SEGMENT STACK DW n DUP(?)段名 ENDS【说明】(1)保留字STACK是堆栈段的专用符号,SEGMENT后面的保留字STACK表明这个段专供堆栈使用。(2)段定义中用“DW n DUP(
3、?)”说明堆栈所用内存区的大小为2n字节,其中n是一个常量。可根据程序需要,调节堆栈段的大小。因为堆栈只能存放字型数据,所以习惯上都是用DW伪指令来定义栈的大小。这不并是说用其它伪指令不行。(3)按基本格式定义的栈是一个空栈,栈中没有存放有效数据。(4)为了使SS和SP在程序执行时取得正确的值,必须在源程序中写一条伪指令:ASSUME SS:堆栈段段名 但不需要像DS和ES一样在程序中用指令进行赋值。对SS和SP的赋值是由操作系统在把执行程序调入内存时由DOS本身完成的,DOS将把SS赋值为堆栈段的段地址,把SP赋值为2n。6.1.2 进栈与出栈指令进栈与出栈指令 栈操作指令以它特有的方式存取
4、数据,属于数据传递类指令,但又与MOV等指令有很大的区别。6.1.2.1 PUSH指令指令 【指令格式】PUSH d 【功能】先把SP的值减去2,然后把操作数d指明的字型数据放入以SS为段地址、SP为偏移地址所对应的内存单元中。【说明】(1)这是单操作数指令,操作数d可以是包括段寄存器在内的任何字型寄存器,或者内存型寻址方式,但不能是立即寻址,当使用内存型寻址方式时可以使用段跨越。(2)PUSH指令的功能包括移动栈顶和存入数据两部分,两部分连续完成,密不可分。(3)操作数d进栈是以减2以后的SP的值作为偏移地址,但程序中不允许出现SP的写法。不要与基地址寄存器或变址寄存器用作偏地址时的写法相混
5、淆,也就是说,把PUSH指令理解成下面两条指令的组合是不正确的:SUB SP,2 MOV SP,d 因为指令“MOV SP,d”存在语法错误。(4)PUSH指令会导致栈顶指针的移动,如果用PUSH指令把很多数据进栈,使SP不断减2,就有可能超出栈的有效范围。在一些高级语言中这种现象会导致堆栈溢出错误,但8088对此并不做任何检测和警告。因此要求编程人员自己注意控制堆栈的大小,估计可能进栈的数据量,以免由于栈溢出导致一些不可预测的后果。6.1.2.2 POP指令指令 【指令格式】POP d 【功能】从SS为段地址、SP为偏移地址对应的内存中取出一个字型数据,送到操作数d指定的位置,然后把SP的值
6、加2。对操作数d的寻址方式要求与PUSH指令相同。堆栈通常用于临时保存数据。一般做法是先用PUSH指令把需要保存的数据入栈,然后完成一定的指令序列,再用POP指令把原先保存的数据出栈。用堆栈保存数据的特点是不用定义变量,不必关心被保存的数据到底在栈的什么位置,只要保证出栈和进栈的对应关系即可。当CPU中的寄存器不够使用时经常用堆栈临时保存数据。栈顶所指位置以上的部分是堆栈的空闲区,以下部分是已入栈的数据存放区(见图6.1),例6.1用来说明PUSH指令和POP指令对堆栈的影响。【例6.1】设AX4F8AH,BX307CH,SP1000H,分别逐条执行下列指令,用内存图的形式画出堆栈的变化情况,
7、并分析程序段执行完后AX和BX寄存器的值。PUSH AX PUSH BX POP AX POP BX 【解】堆栈变化见图6.2,程序段执行完后AX307CH,BX4F8AH。XX 0FFC XX 0FFD XX 0FFE XX 0FFF YY 1000 SP SP XX 0FFC XX 0FFD 8A0FFE 4F 0FFF YY 1000 7C 0FFC 30 0FFD8A 0FFE 4F 0FFF YY 1000 SP(a)执行前 (b)PUSH AX后 (c)PUSH BX后 XX 0FFC XX 0FFC XX 0FFD XX 0FFDSP 8A0FFE XX 0FFE 4F0FFF
8、XX 0FFF YY 1000 SP YY 1000 (d)POP BX后 (e)POP AX后 注:XX表示栈空闲区填充的无用数据,YY表示栈中已存放的有效数据图6.2 执行PUSH和POP指令对堆栈的影响 6.1.2.3 PUSHF和和POPF指令指令 【指令格式】PUSHF 【功能】把SP的值减2,并把16位的标志寄存器送入SS:SP所指向的内存,即把标志寄存器入栈。【指令格式】POPF 【功能】把栈顶的一个16位的字型数据送入标志寄存器,并把SP的值加2。这两条指令相互配合可以设置标志寄存器中的任意一个标志位,一般的做法是:PUSHF POPAX ;按标志位的分布情况和实际需要,修改A
9、X中的值 PUSHAX POPF6.2 子程序的基本格式和有关指令子程序的基本格式和有关指令 6.2.1 汇编语言子程序格式汇编语言子程序格式 子程序是具有固定功能的程序段,并且有规定的格式。不同的计算机语言对子程序格式的规定不同,汇编语言的子程序基本格式如下:子程序名 PROC 类型 指令序列 子程序名 ENDP 格式中的首尾两行表示一个子程序的开始和结束,都属于伪指令。“子程序名”是一个标识符,是编程者给子程序起的名字。子程序名同时还代表子程序第一条指令所在的逻辑地址,称为子程序的入口地址。“类型”只有NEAR和FAR两种,它将影响汇编程序对子程序调用指令CALL和返回指令RET的翻译方式
10、。被夹在子程序起止伪指令之间的指令序列是完成子程序固定功能的程序段,通常指令序列的最后一条指令是返回指令RET。6.2.2 子程序相关指令子程序相关指令 6.2.2.1 CALL指令指令 【指令格式】CALL 子程序名 【功能】这是调用子程序的指令。根据被调用的子程序的类型不同,CALL指令的功能分为两种情况:(1)如果被调用的子程序是NEAR类型,则先把当前指令指针IP的值入栈,这会使SP的值减2,然后把IP改成子程序的第1条指令的偏移地址。(2)如果被调用的子程序是FAR类型,则先把当前CS寄存器的值入栈,再把IP入栈,结果会使SP的值减4,然后把CS和IP改为子程序第1条指令的逻辑地址。
11、CALL也是一种跳转指令,与无条件跳转及条件跳转指令不同的是,CALL在跳转之前先预留了回来的方法,把IP的当前值或CS与IP的当前值入栈保存。从CS与IP 的作用可以知道,它们存放的是正在执行的指令的下一条指令的逻辑地址,现在这一地址被保存在堆栈中。于是回来的方法就显而易见了,只要从栈中取出逻辑地址值,送回IP或者CS与IP即可。这种返回操作就是由RET指令实现的。6.2.2.2 RET指令指令 【指令格式】RET 【功能】这是子程序返回指令,必须写在子程序的指令序列之中。根据所在的子程序的类型不同,RET指令的功能也分为两种情况:(1)如果RET所在子程序是NEAR类型,则从堆栈中出栈一个
12、字(当然,SP会加2),送给IP。(2)如果RET所在子程序是FAR类型,则先从堆栈中出栈一个字送到IP,再出栈一个字送到CS,栈顶指SP的值加4。CALL指令和RET指令都具有跳转的能力,与条件跳转及无条件跳转一样,都是通过修改IP或者CS与IP来实现的。不论跳转是由哪一条指令造成的,对于只改变IP 的跳转,跳转的目的地与跳转指令必然在同一个代码段内,这种跳转称为段内跳转。相应地,CALL指令功能的第一种情况称为段内调用,RET指令功能的第一种情况称为段内返回。另一种跳转是同时改变了CS和IP的值,这就允许跳转指令与跳转目的地不在同一个段中,使得跳转的目的地可以在整个内存空间的任何位置,这一
13、类跳转称为段间跳转。CALL指令功能的第二种情况称为段间调用,RET指令功能的第二种情况称为段间返回。6.2.3 子程序的调用与返回子程序的调用与返回 在汇编语言程序中,子程序分为定义和使用两部分。在较短的程序中,通常把子程序与其余指令写在同一个代码段内,一个代码段中可以定义多个子程序,并且都定义成NEAR类型。这样编写的代码段的基本结构如下:段名段名 SEGMENT 子程序1 PROC NEAR 子程序1 ENDP 子程序2 PROC NEAR 子程序2 ENDP 子程序n PROC NEAR 子程序n ENDP 入口标号:段名 ENDS 从入口标号起的程序段是主程序。RET指令必须出现在子
14、程序中,而CALL指令可以出现在代码段的任何地方。主程序可以调用子程序,一个子程序可以调用另一个子程序,还可以调用它自身,并且在书写次序上没有“先定义后调用”的限制。源程序中的指令段在经过汇编程序的翻译后,所有伪指令都不存在了。作为CALL指令的操作数,“子程序名”部分会翻译成子程序第一条指令的逻辑地址。当计算机在执行CALL 指令时,CS和IP已经是下一条指令的逻辑地址。CALL指令具有保存当前IP或者CS和IP并修改它们的值的能力,因此CALL执行完后,会按照新的CS及IP,转去执行子程序的第一条指令,并依次执行后续指令,完成子程序的功能,直至遇到RET指令。RET指令的执行效果是从栈中取
15、出由CALL保存的数据,恢复在执行CALL指令时的IP或者CS与IP值,从而回到CALL指令的下一行继续执行。【例6.2】分析下面的程序段的执行过程,以及在执行过程中堆栈及指令指针IP的变化情况,并假设在执行CALL指令前,SP的值是0FEH。subp PROC NEAR INC AL ;假设本指令所在的偏移地址是1234H RET subp ENDP CALL subp MOV AX,BX ;假设本指令所在的偏移地址是5678H 【解】【解】(1)当计算机把CALL subp对应的机器指令取到CPU中时,IP的值已经是CALL的下一行的MOV指令所在的偏移地址5678H,此时还未进栈,栈的情
16、况如图6.3(a)所示。(2)由于子程序subp是NEAR类型,按照CALL指令功能的第一种情况执行CALL指令,把IP的值入栈,并把IP的值改为subp子程序的入口地址1234H,此时堆栈的情况如图6.3(b)所示。(3)执行完CALL指令 IP的值已经变成1234H,CS没变,CPU按新的IP值,在CS段下取出一条指令,即INC AL指令。(4)执行INC指令时,CPU自动把IP变成INC的下一行指令的偏移地址,如此逐条执行子程序中的各指令,直至遇到subp子程序的最后一条指令RET。(5)执行RET指令时,堆栈中的情况仍然是图6.3(b),因此执行RET就是取出栈顶所指的一个字,是567
17、8H,并把它送给IP,执行完RET指令后堆栈的情况如图6.3(c)所示。(6)执行完RET指令后,IP的值已经变成5678H,CPU按新的IP值,在CS段下取出一条指令,即MOV AX,BX指令,并继续执行下去。XX 00FA XX 00FA XX 00FA XX 00FB XX 00FB XX 00FB XX 00FC SP 78 00FC XX00FC XX 00FD 56 00FD XX00FD SP YY 00FE YY 00FE SP YY00FE(a)CALL指令执行前 (b)CALL指令执行后 (c)RET指令执行后 图6.3 例6.2的程序执行过程中堆栈的变化情况 例6.2描述
18、了段内调用与返回的过程,对于段间调用与返回,仅仅在CALL指令和RET指令的执行效果上不同,这个问题留给读者:把例6.2中的子程序类型改成FAR,执行过程中栈的变化情况又如何?例6.2中隐藏有一个非常严重的问题,就是如何保证执行完CALL指令后堆栈的情况与执行RET指令前堆栈的情况是相同的。这个问题确实存在,并且是程序员不可回避的。因为完成子程序需要执行多条指令,这些指令中难免会有改变栈顶指针或者改动栈中数据的情况。但是,无论是汇编程序还是计算机硬件本身都对此无能为力,需要程序员自己在编制程序时非常小心。如果不能保证堆栈的情况相同,执行到RET时,计算机仍然按照RET指令本身的功能正常处理,出
19、栈一个字给IP或者连续出栈两个字分别给CS及IP,这时就不会回到调用指令CALL的下一行,而不知跳转到什么地方去了。【注意】为了避免出现这种情况,编制子程序时应该注意以下几点:(1)子程序中的PUSH指令与POP指令数量应该相同,并且存在一一对应关系。(2)不要把SP用作MOV、ADD等指令的目的操作数,不要使用INC SP、DEC SP等指令,不要使用类似指令改变SP的值。(3)不要使用POP SP指令,该指令会用出栈的一个字型数据修改SP,而不像正常的POP指令一样把SP加2。(4)如果子程序中再次用CALL指令去调用子程序,只要被调用的子程序正确,则不会导致出现上述问题。6.3 应用子程
20、序进行编程应用子程序进行编程 6.3.1 子程序实例子程序实例 【例6.3】分析下列程序,描述它的功能。dseg SEGMENT buf DB 80,81 DUP(0)dseg ENDS sseg SEGMENT STACK DW 64 DUP(0)sseg ENDS cseg SEGMENT ASSUME CS:cseg,DS:dseg,SS:sseg cr PROC NEAR MOV AH,2 MOV DL,13 INT 21H MOV DL,10 INT 21H RET cr ENDP main:MOV AX,dseg MOV DS,AX LEA DX,buf MOV AH,10 INT
21、 21H ;输入一个符号串 CALL cr MOV AH,1 INT 21H ;输入一个字符 MOV BL,AL ;用BL保存读入的字符 lab2:MOV DL,SI CMP DL,BL JZ lab1 ;等于第2次输入的符号则转 MOV AH,2 INT 21H INC SI LOOP lab2 lab1:MOV AH,4CH INT 21H cseg ENDS END main6.3.2 对子程序中用到的寄存器进行保护对子程序中用到的寄存器进行保护 【例6.4】设子程序cr的定义如例6.3所示,比较下面两个程序段,分析各自执行完后寄存器AX中的值是多少。(a)MOV AX,102H MOV
22、 BX,304H ADD AX,BX(b)MOV AX,102H MOV BX,304H CALL cr ADD AX,BX 【解】程序段(a)中,先把AX赋值为102H,再把BX赋值为304H,然后用ADD指令把两数相加,和为406H,结果放在ADD指令的目的操作数AX中。程序段(b)的前两行与(a)完全相同,AX取值102H,BX取值304H,但在相加之前调用了子程序cr。从例6.3中cr的具体实现方法可以知道,调用过程中寄存器AH的值被改为2,因为INT 21H输出功能,使AL的值也被修改,变成0AH,并且这个值一直保持到调用结束,于是“CALL cr”指令调用子程序后,AX的值不再是调
23、用前的102H,而变成了20AH,当ADD指令进行两个寄存器相加时,结果是50EH,并放到目的操作数AX中。从例6.4可以看到,两个程序段仅仅相差一个子程序调用,而且子程序cr也只不过完成回车换行的操作,但两个程序段执行的结果却不一样,原因就在于调用子程序前,寄存器AX中放了一个有用的数据102H,但子程序中对AX重新赋了值,破坏了原来的数据。子程序中修改寄存器的值会给程序编制带来很大的麻烦,就如例6.4(b)的情况,想要找出错误的原因是不太容易的。为此,做法之一是在调用前把有用的数据存放到适当的地方保护起来,比如在例6.4(b)的CALL指令之前可以把 AX的值先找一个寄存器(比如SI)临时
24、存放,调用后再取回到AX中;另一个比较好的做法是在子程序中对所有使用到的寄存器进行保护,等到子程序的功能完成后,再恢复这些寄存器的原值,最后以RET指令返回。按照这个原则,把例6.3的子程序cr 改写成如下形式:cr PROC NEAR PUSH AX PUSH DX MOV AH,2 MOV DL,13 INT 21H MOV DL,10 INT 21H POP DX POP AX RET cr ENDP 修改后的子程序cr先把AX和DX的值入栈保护,完成回车换行操作后,再从栈中取出原来保存的数据恢复AX和DX的原值。用堆栈临时保存数据是子程序中普遍使用的一种方法。经过这样的修改,例6.4的
25、两个程序段各自执行后,AX中的值就会是一样的,调用子程序cr进行回车换行操作就不会影响程序的正常执行。【注意】入栈指令PUSH和出栈的POP指令必须一一对应。从栈操作的“先进后出”方式可以知道,入栈次序与出栈次序是相反的,所以PUSH指令序列中操作数的次序与 POP指令序列中操作数的次序相反,就如同上面的子程序cr中两条PUSH指令是先AX 再DX,而两条POP则是先DX再AX。6.3.3 带参数的子程序带参数的子程序 【例6.5】编写一个子程序,对一个无符号的字型数组的各元素求和。在调用子程序之前,已把数组的段地址放在DS中,起始偏移地址放在寄存器SI中,数组元素个数(0)放在CX中。要求子
展开阅读全文