第六章子程序结构.ppt_第1页
第六章子程序结构.ppt_第2页
第六章子程序结构.ppt_第3页
第六章子程序结构.ppt_第4页
第六章子程序结构.ppt_第5页
已阅读5页,还剩71页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1 第六章子程序结构 为了程序共享或模块化设计的需要 可以把一段公共语句序列设计成子程序或宏指令的形式 本章介绍子程序的设计方法 2 6 1子程序结构及设计方法 一 子程序结构在汇编语言中用过程定义伪指令定义子程序 过程定义伪指令格式 过程名PROC属型 过程名ENDP 3 其中过程名就是子程序名 它也表示子程序入口的符号地址 属型可以是NEAR型 缺省值 或FAR型 NEAR型子程序只可以被段内调用 而FAR型子程序可以被段间或段内调用 4 1 调用程序和子程序在同一个代码段的程序结构 子程序类型可缺省 注意END后必须跟主程序名 CODESEGMENTMAINPROCFAR CALLSUB1RETMAINENDPSUB1PROC RETSUB1ENDPCODEENDSENDMAIN 5 2 调用程序和子程序在不同段的程序结构 SUB2既被段间调用又被段内调用 必须是FAR属性 CALL要显式说明是FAR属性 CODE1SEGMENTMAINPROCFARCALLFARPTRSUB2RETMAINENDPCODE1ENDSCODE2SEGMENTSUB1PROCFARCALLFARPTRSUB2RETSUB1ENDPSUB2PROCFARRETSUB2ENDPCODE2ENDSENDMAIN 6 二 设计子程序时应注意的问题 1 子程序说明为便于引用 子程序应在开头对其功能 调用参数和返回参数等予以说明 例如参数的类型 格式及存放位置等 7 2 寄存器的保存与恢复为了保证调用程序的寄存器内容不被破坏 应在子程序开头保存它要用到的寄存器内容 返回前再恢复它们 通常用PUSH指令保存 用POP恢复 注意由于堆栈操作采用后进先出的规则 一般说来 弹出寄存器的顺序应该与压入时的相反 8 3 密切注意堆栈状态在设计含有子程序的程序时 要密切注意堆栈的变化 这包括要注意一切与堆栈有关的操作 例如CALL调用类型和子程序定义类型的一致性 PUSH和POP指令的匹配 通过堆栈传递参数时子程序返回使用RETn指令等 以确保堆栈平衡 否则后果不可预料 9 6 2子程序参数传递 可以通过给子程序传递参数使其更通用 常用的参数传递方法如下 通过寄存器传递 若调用程序和子程序在同模块 源程序 中 子程序可以直接访问模块中的变量 通过地址表传递参数地址 通过堆栈传递参数或参数地址 10 1 通过寄存器传递这种传递方式使用方便 适用于参数较少的情况 例1 把BX中的16位二进制数转换成十进制并显示在屏幕上 分析 二进制到十进制数的转换方法有多种 本例采用从高到低逐个除以十进制位权的方法 11 STASGSEGMENTDW32DUP STASGENDSCODESEGMENTASSUMECS CODEMAINPROCFARMOVBX 162EHCALLTERNMOVAX 4C00HINT21HMAINENDP 程序6 3 12 TERNPROC 二 十并显示 MOVCX 10000CALLDEC DIV 转换万位数MOVCX 1000CALLDEC DIV 转换千位数MOVCX 100CALLDEC DIV 转换百位数MOVCX 10CALLDEC DIV 转换十位数MOVCX 1CALLDEC DIV 转换个位数RETTERNENDP 13 DEC DIVPROC CX中为十进制的位权MOVAX BXMOVDX 0DIVCX 商为转换后的一位十进制数MOVBX DXMOVDL ALADDDL 30H 转换成ASCII码MOVAH 2 显示INT21HRETDEC DIVENDPCODEENDSENDMAIN 14 2 若调用程序和子程序在同模块中 子程序可以直接访问模块中的变量例2 实现数组求和功能 要求数组求和 不考虑溢出情况 由子程序实现 其数组元素及结果均为字型数据 见程序6 4 15 STACKSGSEGMENTSTACK STK DW32DUP S STACKSGENDSDATASEGMENTARYDW1 2 3 4 5 6 7 8 9 10COUNTDW ARY 2 数组元素个数SUMDW 数组和的地址DATAENDS 程序6 4 16 CODE1SEGMENTMAINPROCFARASSUMECS CODE1 DS DATAPUSHDSXORAX AXPUSHAXMOVAX DATAMOVDS AXCALLFARPTRARY SUMRETMAINENDPCODE1ENDS 17 CODE2SEGMENTASSUMECS CODE2ARY SUMPROCFAR 数组求和子程序PUSHAX 保存寄存器PUSHCXPUSHSILEASI ARY 取数组起始地址MOVCX COUNT 取元素个数XORAX AX 清0累加器 18 NEXT ADDAX SI 累加和ADDSI TYPEARY 修改地址指针LOOPNEXTMOVSUM AX 存和POPSI 恢复寄存器POPCXPOPAXRETARY SUMENDPCODE2ENDSENDMAIN 19 3 通过地址表传递参数地址适用于参数较多的情况 具体方法是先建立一个地址表 该表由参数地址构成 然后把表的首地址通过寄存器或堆栈传递给子程序 例3 编写一个数组求和子程序 其数组元素及结果均为字型数据 另定义两个数组 并编写一个主程序 通过调用数组求和子程序分别求出两个数组的和 20 分析 虽然主 子程序在同模块中 但由于在一个程序中要分别求出两个数组的和 因此子程序不能直接引用数组变量名 本例用数组首地址 元素个数的地址 和地址构成地址表 通过地址表传送这些参数的地址 以便子程序能够访问到所需参数 见程序6 5 21 程序6 5 STACKSGSEGMENTSTACK STK DW32DUP S STACKSGENDSDATASEGMENTARYDW1 2 3 4 5 6 7 8 9 10 数组1COUNTDW ARY 2 数组1的元素个数SUMDW 数组1的和地址NUMDW10 20 30 40 50 数组2CTDW NUM 2 数组2的元素个数TOTALDW 数组2的和地址TABLEDW3DUP 地址表DATAENDS 22 CODE1SEGMENTMAINPROCFARASSUMECS CODE1 DS DATAPUSHDSXORAX AXPUSHAXMOVAX DATAMOVDS AX 构造数组1的地址表MOVTABLE OFFSETARYMOVTABLE 2 OFFSETCOUNTMOVTABLE 4 OFFSETSUMLEABX TABLE 传递地址表首地址CALLFARPTRARY SUM 23 构造数组2的地址表MOVTABLE OFFSETNUMMOVTABLE 2 OFFSETCTMOVTABLE 4 OFFSETTOTALLEABX TABLE 传递地址表的首地址CALLFARPTRARY SUM 段间调用调用数组求和子程序RETMAINENDPCODE1ENDS 24 CODE2SEGMENTASSUMECS CODE2ARY SUMPROCFAR 数组求和子程序PUSHAX 保存寄存器PUSHCXPUSHSIPUSHDIMOVSI BX 取数组起始地址MOVDI BX 2 取元素个数地址MOVCX DI 取元素个数MOVDI BX 4 取结果地址XORAX AX 清0累加器 25 NEXT ADDAX SI 累加和ADDSI TYPEARY 修改地址指针LOOPNEXTMOV DI AX 存和POPDI 恢复寄存器POPSIPOPCXPOPAXRETARY SUMENDPCODE2ENDSENDMAIN 26 4 通过堆栈传递参数或参数地址这种方式适用于参数较多 或子程序有多层嵌套 递归调用的情况 步骤 主程序把参数或参数地址压入堆栈 子程序使用堆栈中的参数或通过栈中参数地址取到参数 子程序返回时使用RETn指令调整SP指针 以便删除堆栈中已用过的参数 保持堆栈平衡 保证程序的正确返回 27 例4 完成数组求和功能 求和由子程序实现 要求通过堆栈传递参数地址 STACKSGSEGMENTSTACK STK DW16DUP STACKSGENDSDATASEGMENTARYDW1 2 3 4 5 6 7 8 9 10COUNTDW ARY 2SUMDW DATAENDS 28 CODE1SEGMENTMAINPROCFARASSUMECS CODE1 DS DATAPUSHDS XORAX AXPUSHAX MOVAX DATAMOVDS AX 29 LEABX ARYPUSHBX 压入数组起始地址LEABX COUNTPUSHBX 压入元素个数地址LEABX SUMPUSHBX 压入和地址CALLFARPTRARY SUM 调用求和子程序RET MAINENDPCODE1ENDS 30 CODE2SEGMENTASSUMECS CODE2ARY SUMPROCFAR 数组求和子程序PUSHBP 保存BP值MOVBP SP BP是堆栈数据的地址指针PUSHAX 保存寄存器内容PUSHCX PUSHSI PUSHDI MOVSI BP 10 得到数组起始地址MOVDI BP 8 得到元素个数地址MOVCX DI 得到元素个数MOVDI BP 6 得到和地址XORAX AX 31 NEXT ADDAX SI 累加ADDSI TYPEARY 修改地址指针LOOPNEXTMOV DI AX 存和POPDI 恢复寄存器内容POPSI POPCX POPAX POPBP RET6 返回并调整SP指针ARY SUMENDPCODE2ENDSENDMAIN 32 本例通过BP访问堆栈中的参数 程序的堆栈变化情况参见图6 1 指示了程序中所有入栈操作对堆栈的影响随入栈数据的增加 SP的值不断减小 堆栈可用空间也随之减少 图6 2为已从子程序返回 而主程序RET指令执行前的堆栈状态 其中的灰色部分表示执行语句 时已弹出的数据 33 随着弹出数据的增加 SP的值不断增大 堆栈可用空间也随之增大 子程序中语句 RET6指令 在从堆栈弹出返回地址后还要使SP值加6 这样就跳过了通过堆栈传递的三个参数 或者说删除了它们 因此 当主程序的语句 RET指令被执行时 程序控制从栈顶弹出数字0给IP 弹出PSP的段基址给CS 于是执行PSP 0处的INT20H指令 正确返回操作系统 34 返回 35 返回 36 从以上分析可以看出 通过堆栈传递参数时子程序的返回指令必须是RETN形式 当堆栈操作是16位时N值应该是压入堆栈的参数个数的2倍 只有这样保证程序的正常运行 用结构形式访问堆栈中的参数 这种方法更简便及规范化 37 例5 完成数组求和功能 其中求和由子程序实现 要求使用结构访问堆栈中的参数 图6 3给出了堆栈及结构数据定义 注意这些结构字段的顺序为其值压入的逆序 实际上 它只是给图6 1中由主程序压入的数据 返回地址及子程序压入的BP值起了个名字而已 而字段值的预置是通过PUSH和CALL指令实现的 当子程序用到堆栈中的参数时 只需使用BP作为基地址 通过结构字段名访问就可以了 编码见程序6 7 38 返回 39 程序6 7 STACKSGSEGMENTSTACK STK DW16DUP S STACKSGENDSDATASEGMENTARYDW1 2 3 4 5 6 7 8 9 10COUNTDW ARY 2SUMDW DATAENDS 40 CODE1SEGMENTMAINPROCFARASSUMECS CODE1 DS DATAPUSHDSXORAX AXPUSHAXMOVAX DATAMOVDS AX 41 LEABX ARYPUSHBX 压入数组起始地址LEABX COUNTPUSHBX 压入元素个数地址LEABX SUMPUSHBX 压入和地址CALLFARPTRARY SUMRETMAINENDPCODE1ENDS 42 CODE2SEGMENTASSUMECS CODE2STACK STRCSTRUC 定义结构SAVE BPDW SAVE CS IPDW2DUP SUM ADDRDW COUNT ADDRDW ARY ADDRDW STACK STRCENDS 43 ARY SUMPROCFAR 数组求和子程序PUSHBP 保存BP值MOVBP SPPUSHAXPUSHCXPUSHSIPUSHDIMOVSI BP ARY ADDR 数组始地址MOVDI BP COUNT ADDRMOVCX DI MOVDI BP SUM ADDR 得到和地址XORAX AX 44 NEXT ADDAX SI 累加ADDSI TYPEARY 修改地址指针LOOPNEXTMOV DI AX 存和POPDIPOPSIPOPCXPOPAXPOPBPRET6 返回并调整SP指针ARY SUMENDPCODE2ENDSENDMAIN 45 例6 编写两个四位无符号十进制数乘法程序 要求 乘数从键盘输入 二进制乘 用十进制数形式显示乘积 1 分析 由于题目要求从外设输入输出数据 而在内存中用二进制数形式实现乘法 所以涉及到代码转换问题 步骤如下 从键盘输入两个十进制乘数 ASCII码 分别转换成二进制形式 二进制数乘 把二进制乘积转换成十进制数的ASCII码形式 输出到屏幕 46 2 设计 本例中的输入 输出 十进制到二进制的转换 二进制到十进制的转换均采用子程序形式实现 十 二转换算法 Y Y 10 Xi Y的初始值为0 i n n 1 0 ASC BIN是实现四位十进制数转换成二进制数的子程序 程序中Y值在AX中 i值由CL控制 47 二 十转换算法 除10取余法 即用32位的二进制乘积 高16位在RSLTHI中 低16位在RSLTLO中 作为被除数 10作为除数 每次除后所得到的余数就是本位的十进制数 并把它转换成ASCII码存放在输出缓冲区的相应位置 直到整个转换结束 最后输出 48 BIN ASC是实现32位二进制数转换成十进制数的子程序 考虑到32位的被除数除以10很容易产生超过16位的商 从而产生除法错中断无法得到正确结果 因此需要采用一种避免产生除法错中断的技术 本例采用的方法是把32位被除数扩展成48位 其中最高16位为0 然后进行除法运算 示意图如下 49 3 编码 程序6 8 CREQU0DHLFEQU0AHSTACKSGSEGMENTSTACK S DW64DUP ST STACKSGENDS 50 DATASEGMENTPROMPT1DBCR LF INPUTNUM1 PROMPT2DBCR LF INPUTNUM2 ASCIN1DB5 5DUP ASCIN2DB5 5DUP BIN1DW 乘数1二进制值BIN2DW 乘数2二进制值RSLTHIDW0 32位二进制乘积的高16位RSLTLODW0 32位二进制乘积的低16位ASCOUT0DBCR LF RESULT ASC OUTDB10DUP 0 DATAENDS 51 CODESEGMENTASSUMECS CODE DS DATA SS STACKSGMAINPROCFARMOVAX DATAMOVDS AXLEADX PROMPT1CALLDISPLEADX ASCIN1CALLINPUT 输入乘数1LEADX PROMPT2CALLDISPLEADX ASCIN2CALLINPUT 输入乘数2 52 LEASI ASCIN1 1 建立乘数1缓冲区的地址指针CALLASC BIN 把乘数1转换成二进制数MOVBIN1 AX 存乘数1的二进制值LEASI ASCIN2 1 建立乘数2缓冲区的地址指针CALLASC BIN 把乘数2转换成二进制数MOVBIN2 AX 存乘数2的二进制值MOVAX BIN1MULBIN2 乘数1 乘数2 结果为32位二进制数 53 MOVRSLTLO AX 保存结果的低16位MOVRSLTHI DX 保存结果的高16位CALLBIN ASC 调用32位二进制数转换成十进制数子程序LEADX ASCOUT0CALLDISP 显示十进制乘积MOVAX 4C00HINT21HMAINENDP 54 DISPPROC 显示字符串子程序MOVAH 9INT21HRETDISPENDPINPUTPROC 输入字符串子程序MOVAH 0AHINT21HRETINPUTENDP 55 ASC BINPROC 十进制数转换成16位二进制数子程序 SI指向十进制数缓冲区 其中第一个字节存放要转 换的十进制位数 从第二个字节开始存放着十进制 数的ASCII码 AX中存放转换结果 XORAX AXMOVCL SI XORCH CHINCSIJCXZM2 56 M1 MOVBX 10MULBXMOVBL SI 得到一位十进制数的ASCII码INCSI 修改地址指针ANDBX 0FH 把十进制数的ASCII码转换成十进制数ADDAX BXLOOPM1M2 RETASC BINENDP 57 BIN ASCPROC 32位二进制数转换成十进制数子程序LEADI ASC OUT 9 DI指向十进制数串的个位MOVBX 10C0 MOVDX 0MOVAX RSLTHICMPAX 0JEC1 58 DIVBXMOVRSLTHI AXMOVAX RSLTLODIVBXMOVRSLTLO AXORDL 30HMOV DI DLDECDIJMPSHORTC0 59 C1 MOVAX RSLTLOC2 CMPAX 0JZC3MOVDX 0DIVBXORDL 30HMOV DI DLDECDIJMPSHORTC2C3 RETBIN ASCENDPCODEENDSENDMAIN 60 以上程序是运行在16位操作数模式的情况 但它的算法思想对更多位操作数也适用 61 6 3嵌套与递归子程序 一 子程序嵌套在汇编语言中 允许子程序作为调用程序去调用另一子程序 把这种关系称为子程序嵌套 图6 4为子程序嵌套示意图 嵌套的层数没什么限制 其层数称为嵌套深度 上一节讨论的普通子程序设计中应注意的问题对嵌套子程序也适用 62 返回 63 由于子程序嵌套对堆栈的使用很频繁 因此还要确保堆栈有足够空间 并要注意堆栈的正确状态 这包括CALL RET RETN PUSH POP INT IRET等与堆栈操作有关指令的正确使用 总之 在子程序嵌套中使用堆栈更要小心 以免由于堆栈使用不当而导致程序不能正常运行的情况发生 子程序嵌套举例见6 2节的程序6 3 它的嵌套深度是2 64 二 递归子程序 在子程序嵌套的情况下 如果一个子程序调用的子程序就是它自身 这样的子程序称为递归子程序 显然递归调用是子程序嵌套的一种特殊情况 使用递归算法往往能设计出效率较高的程序 递归调用最简单例子是计算阶乘 求N 本身是一个子程序 由于N 是N和 N 1 的乘积 所以为求 N 1 必须递归调用求N 的子程序 只是每次调用所使用的参数不同而已 以下是求N 递归过程的描述 65 BEGINFACT N RESULT SAVEREGISTERONSTACKIFN 0RESULT 1ELSEPUSHADDRESSOFRESULTONTOSTACKPUSHN 1ONTOSTACKCALLFACT N 1 RESULT RESULT N RESULTENDIFRESTOREREGISTERSFROMSTACKDELETEPARAMETERSFROMSTACK 66 设计递归子程序时 必须保证每一次后继调用都不能破坏它上一次调用时所生成的参数和中间结果 并且该过程不会修改它本身 这就意味着当使用汇编语言设计递归子程序时 必须考虑每一次调用都应该把它的参数 寄存器和所有的中间结果保存到不同的存储区域 最好的办法是利用堆栈来存储这些信息 一次调用所保存的信息称为一帧 67 可以利用BP作为基址寄存器访问堆栈中的帧数据 递归调用要使用大量的堆栈空间 一定要保证堆栈足够大 而且也要保证堆栈的正确使用 避免死机等不可预料的情况发生 另外 为了在达到嵌套深度时能够停止递归调用 避免死循环 应在不满足递归条件时安排一条条件转移指令退出嵌套 并保证能按反向次序退出 返回主程序 图6 5是能够满足上述要求的N 流程图 程序6 9是其编码实现 68 返回 69 程序6 9 STACKSGSEGMENTSTACK S

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论