第6章-子程序设计_第1页
第6章-子程序设计_第2页
第6章-子程序设计_第3页
第6章-子程序设计_第4页
第6章-子程序设计_第5页
已阅读5页,还剩32页未读 继续免费阅读

下载本文档

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

文档简介

第六章子程序结构

过程定义伪操作

子程序的调用与返回保存与恢复寄存器子程序的参数传送

子程序的嵌套与递归一、调用程序与子程序

子程序:在许多应用程序中,常常需要多次使用某功能的指令序列。这时,为了减少重复编写程序,节省内存空间,把这一功能的指令序列组成一个相对独立的程序段。在程序运行时,如果需要使用这个给定的功能,就转移到这个独立的程序段,待这个独立的程序段指令序列执行完后,又返回到原来位置继续运行程序。我们把这个相对独立的程序段就叫子程序或过程。调用程序:编制程序时,按需要转向子程序,称为子程序调用,或称为过程调用。调用子程序的程序称为调用程序或主程序。主、子程序是相对而言的。但子程序一定是受调用程序或主程序调用的。子程序定义的位置可以放在主程序的前面或后面。二.过程定义伪操作过程名PROCNEAR

(FAR)

过程名ENDP(1)NEAR属性:调用程序和子程序在同一代码段中,可省略。(段内调用)(2)FAR属性:调用程序和子程序不在同一代码段中。(段间调用)...code1segmentmainprocfar……

callfarptr

subp……retmainendpcode1endscode2segmentsubpprocfar……

retsubp

endpcode2ends

段间调用和返回codesegmentmainprocnear……call

subp……retmainendpsubpprocnear……

retsubp

endpcodeends

段内调用和返回segxsegmentsubtprocfar……

retsubt

endp……

call

subt……segxendssegysegment……

callfarptr

subt……segyends同一个子程序可以被段内调用,也可以段间调用

(1)CALL子程序调用指令:隐含使用堆栈保存返回地址

指令格式:

CALLDST;其中DST为过程的目标地址(即过程名)。指令功能:把CALL指令的下一条指令地址(称为返回点或断点)推入堆栈保存,然后转到目标地址(DST)。

CALL指令可以在段内、段间调用,寻址方式分为直接和间接两种。三.子程序的调用与返回指令

段内直接近调用:CALLDST执行操作:

(SP)←(SP)-2((SP)+1,(SP))←(IP)(IP)←DST(实际操作是把从指令中得到的距目标过程相对偏移量(最大为32K字节)加到指令指针IP上(得到子程序的入口地址),实现过程调用。)段内间接近调用:CALLDST执行操作:

PUSHIP(IP)←(EA)DST给出子程序的入口地址(子程序为near属性)比如:CALLsubpDST给出寄存器或存储单元的内容(转向地址)比如:CALLwordptr[bx](SP)→(IP)段间直接远调用:CALLFARPTRDST执行操作:PUSHCSPUSHIP(IP)←DST偏移地址

(CS)←DST段地址段间间接远调用:CALLFARPTRDST

执行操作:PUSHCSPUSHIP(IP)←(EA)(CS)←(EA+2)(IP)(CS)(SP)→DST给出子程序的入口地址(子程序为far属性)比如:CALLfarptr

subp(2)RET返回主程序指令:属于无条件转移指令。可以在段内或段间返回。段内近返回:RET

执行操作:POPIP段内带立即数近返回:RETEXP

执行操作:POPIP(SP)(SP)+EXP段间远返回:RET(F)

执行操作:POPIPPOPCS段间带立即数远返回:RETEXP表达式(SP)→(IP)(SP)→

。。。(SP)→(SP)+EXP例:带立即数返回(SP)

堆栈段codesegmentmainprocfarStart:pushds Subax,ax pushax……pushaxpushbxpushcxcallsub……retmainendpsubprocnear……ret6subendpcodeendsendstart(IP)(cx)(bx)(ax)(SP)

(SP)

主程序模块子程序模块(SP)

程序返回【例】已知三个八位无符号数X、Y、Z,分别存放于BUF、BUF+1和BUF+2存储单元,计算2X+5Y+8Z,结果送RES和RES+1单元。

DATASEGMENTBUFDB71H,0A4H,9BHRESDB2DUP(?)DATAENDSSTACKSEGMENTDW50DUP(?)TOPLABELWORDSTACKENDSCODESEGMENTASSUMECS:CODE,DS:DATA,SS:STACKMAINPROCFARSTART:…MAINENDP…CODEENDSENDSTART将主程序补充完整START:PUSH DSSUB AX,AXPUSHAX;保存程序前缀PSP地址

MOVAX,DATAMOVDS,AXMOVAX,STACKMOVSS,AXMOVSP,OFFSETTOPMOVAX,0;AX清0MOVWORDPTRRES,AX;RES字单元清0LEABX,BUF;置地址指针

MOVAL,2

CALLMULL;过程调用计算RES+2XMOVAL,5

CALLMULL;过程调用计算RES+5YMOVAL,8

CALLMULL;过程调用计算RES+8ZRET;程序返回子程序编写MULLPROCNEAR;乘法子程序

MULBYTEPTR[BX];做乘法结果在AXADDWORDPTRRES,AX;做加法

MOVAX,0;AX清0INCBX;地址加1RET

;返回主程序MULLENDP四.现场保护与恢复subtprocnear

pushaxpushbxpushcxpushdx…………popdxpopcxpopbxpopaxretsubt

endp要保护的寄存器:应该是在子程序中将被使用,返回调用程序后仍然需要使用其原有内容的那些寄存器。即保护调用程序和子程序两者在使用上发生冲突的那些寄存器。但在编程时,一时很难弄清哪些是有冲突的寄存器,一种较为简单的方法是把所有的寄存器均加以保护。一般在子程序中进行寄存器保护较好。即在子程序的开始部分,先进行相关寄存器(主要是在子程序中使用的各寄存器)的保护。然后再进行子程序的处理操作。在执行完子程序后,返回前,先恢复各寄存器内容后,再返回调用程序。(1)通过寄存器传送参数(2)通过存储器传送参数(3)通过地址表传送参数地址(4)通过堆栈传送参数或参数地址五.子程序的参数传送实现的方法是把子程序所需要的入口参数,由调用程序予先放入指定的寄存器中。在进入子程序后,子程序就可直接对这些寄存器内容进行操作了。同样子程序的运行结果,也可置入寄存器中,把它们作为子程序的出口参数寄存器使用。请大家注意:参数的传递方法并不是固定不变的,即它们是可以综合使用的。依实现的需要和情况的不同,可以灵活使用其中一种方式,也可以同时使用几种方式的混合。有的时候还可能并不需要参数传递。例1:十六进制到十进制的转换(通过寄存器传送参数)hexidecsegment;1610assumecs:hexidec

mainprocfarstart:pushdssubax,axpushaxcallhexibin

;162callcrlfcallbinidec

;210callcrlf

retmainendp………………hexidecendsendstartCrlfprocnear

movdl,0dh

movah,2

int21h

movdl,0ah

movah,2

int21hretCrlf

endphexibinprocnear

mov

bx,0newchar:

movah,1

int21hsubal,30h

jlexit

cmpal,10

jladd_tosubal,27h

cmpal,0ah

jlexit

cmpal,10h

jgeexitadd_to:

mov

cl,4

shl

bx,cl

movah,0addbx,ax

jmp

newcharexit:rethexibin

endpbinidecprocnear

mov

cx,10000d

calldec_div

mov

cx,1000d

calldec_div

mov

cx,100d

calldec_div

mov

cx,10d

calldec_div

mov

cx,1d

calldec_div

retbinidec

endpdec_divprocnear

movax,bx

mov

dx,0divcx

mov

bx,dx

movdl,aladddl,30h

movah,2

int21hretdec_div

endp1ab5

31616235例2:累加数组中的元素(通过存储器传送参数)datasegment

ary

dw1,2,3,4,5,6,7,8,9,10countdw10sumdw?dataendscodesegmentassumecs:code,ds:datastart:

movax,data

mov

ds,ax

callproadd

mov ax,4c00h

int21hmainendpcodeendsendstartproaddprocnearpushaxpushcxpushsileasi,ary

mov

cx,count

xorax,axnext:addax,[si]addsi,2loopnext

mov

sum,axpopsipopcxpopaxretproadd

endp如果数据段定义如下:datasegment

ary

dw1,2,3,4,5,6,7,8,9,10

countdw10

sumdw?

ary1dw10,20,30,40,50,60,70,80,90,100

count1dw10

sum1dw?dataends如果直接访问内存变量,那么累加数组ary和数组ary1中的元素不能用同一个子程序

proadd。那如何能实现调用同一个子程序proadd来累加另一个数组元素?例3:累加数组中的元素(通过地址表传送参数地址)datasegment

ary

dw10,20,30,40,50,60,70,80,90,100countdw10sumdw?

tabledw3dup(?)

;地址表dataendscodesegmentmainprocfarassumecs:code,ds:datapushdssubax,axpushax

movax,data

mov

ds,ax

movtable,offsetary

;把要传送给子程序

movtable+2,offsetcount;的参数都存放在地址

movtable+4,offsetsum

;表中,然后把地址表

mov

bx,offsettable;的首地址通过寄存器

callproadd

;BX传送到子程序中去。

retmainendp

proaddprocnearpushaxpushcxpushsipushdi

mov

si,[bx]

mov

di,[bx+2]

mov

cx,[di]

mov

di,[bx+4]

xorax,axnext:addax,[si]addsi,2loopnext

mov[di],axpopdipopsipopcxpopaxretproadd

endpcodeendsendmain30d40d50d60d70d80d90d100d10d

ary

20d10dcountsumtable0000001400160018000000140016

(bx)(si)(di)550d存储单元的内容例4:累加数组中的元素(通过堆栈传送参数地址)

在主程序里把参数地址保存到堆栈中,在子程序里从堆栈中取出参数以达到传送参数的目的。datasegment

ary

dw10,20,30,40,50,60,70,80,90,100countdw10sumdw?dataendsstacksegment

dw100dup(?);堆栈全是字操作,必须用DW

toslabelword;栈顶指针也必须是字类型stackends

codesegmentmainprocfarassumecs:code,ds:data,ss:stackstart:

movax,stack

mov

ss,ax

movsp,offsettos

;堆栈段及SP设置方法

movax,data

mov

ds,ax

mov

bx,offsetarypushbx

mov

bx,offsetcountpushbx

mov

bx,offsetsumpushbx

;把参数地址保存到堆栈

callproadd……

movax,4c00h

int21hmainendpcodeendsendstartproaddprocnear

pushbp

mov

bp,sppushaxpushcxpushsipushdi

movsi,[bp+8]

movdi,[bp+6]

mov

cx,[di]

movdi,[bp+4]

xorax,axnext:addax,[si]addsi,2loopnext

mov[di],axpopdipopsipopcxpopaxpopbp

ret6proadd

endp(ip)sumcountarray

(di)(si)(cx)(ax)(sp)

(bp)

(bp)

(bp)+8

(bp)+6

(bp)+4

tosCALL指令堆栈最满的状态子程序的嵌套:一个子程序也可以作为调用程序去调用另一个子程序。主程序

子程序A

子程序B六.子程序的嵌套与递归调用……callproc_A……proc_A

……callproc_B……Callproc_Aretproc_B……ret子程序的递归调用是指一个子程序直接或间接地调用自己。递归调用要注意必须有结束递归调用的判断语句。编写子程序计算(bx)!=axFactprocnearand bx,bx

jz fact1;等于0跳转

push bx

dec

bxcall fact;递归调用L:pop bx

mul

bx

;结果在ax中

retFact1:mov ax,1retFactendp编写程序计算4!+7!

….….;主程序开始部分

mov

bx,4;入口参数

call fact;调用子程序M:pushax;保存4!的结果

mov

bx,7

callfact;(ax)=7!popbx;(bx)=4!addax,bx….….递归子程序:n!=n(n-1)(n-2)…13!=3×2×1=6

0!=1n!=n(n-1)!子程序如果段间调用时,必须定义为FAR类型。段间调用通常用于不同模块之间的调用。编写不同模块的段间调用程序,应该注意以下几个个问题:(1)主程序模块和子程序模块分别汇编,然后用连接程序将它们连接在一起。(2)在主程序模块中,主程序所调用的外部过程名必须用EXTRN伪指令说明。(3)在过程模块中,提供给外段调用的过程名必须用PUBLIC伪指令说明。(4)模块间其它公用符号名及外部符号名的定义不可缺少。七.多模块程序设计(段间调用)局部符号:在本模块中定义,在本模块中引用的符号。不同模块中的局部符号允许重名。外部符号:在某一模块中定义,在另一模块中引用的符号。要连接的模块中的外部符号不允许重名。

PUBLIC符号EXTRN符号:类型

extrn

proadd:far……;在本模块中引用其他模块

……;中定义的外部符号proaddcode1segmentstart:……callfarptr

proadd

……code1endsendstart;proadd1.asmpublicproadd……;在本模块中定义可供

……;其他模块引用的符号code2segmentproaddprocfar……retproadd

endpcode2endsend;proadd2.asm将【例6.1】中的段内调用改为段间调用

已知三个八位无符号数X、Y、Z,分别存放于BUF、BUF+1和BUF+2存储单元,计算2X+5Y+8Z,结果送RES和RES+1单元。NAMEEXAM6_2EXTRNMULL:FAR;外部引用说明PUBLICRES;定义公用名DATASEGMENTBUFDB71H,0A4H,9BHRESDB2DUP(?)DATAENDSSTACKSEGMENTDW50DUP(?)TOPLABELWORDSTACKENDSCODE1SEGMENTASSUMECS:CODE1,DS:DATA,SS:STACKMAINPROCFARSTART:…MAINENDPCODE1ENDSENDSTART

NAMEL6_2AEXTRNRES:BYTE;外部引用说明

PUBLICMULL;定义公用名CODE2SEGMENTASSUMECS:CODE2MULLPROCFAR;乘法子程序

MULBYTEPTR[BX];做乘法结果在AXADDWORDPTRRES,AX;做加法

MOVAX,0;AX清0INCBX;地址加1RET;返回主程序MULLENDPCODE2ENDSEND例5:用两个模块累加数组元素。;proadd1.asmextrn

proadd:fardatasegmentcommon;允许同名段重叠

ary

dw1,2,3,4,5,6,7,8,9,10countdw10sumdw?dataendscode1segmentmainprocfarassumecs:code1,ds:datastart:

movax,data

mov

ds,ax

callfarptr

proadd

movah,4ch

int21hmainendpcode1endsendstart;proadd2.asmpublicproadddatasegmentcommon

ary

dw1,2,3,4,5,6,7,8,9,10countdw10sumdw?dataends

code2segmentproaddprocfarassumecs:code2,ds:data

movax,data

mov

ds,ax

pushaxpushcxpushsileasi,ary

mov

cx,count

xorax,axnext:addax,[si]addsi,2loopnext

movsum,ax

popsipopcxpopax

retproadd

endpcode2endsend补充:模块化程序设计思想在设计大型程序时,常常要将整个问题分解为若干个小问题,必要时还要将小问题再次分解为更小的若干问题,每个小问题编写成独立的源文件,最后将所有的源文件连接起来组合成一个大程序。也就是说,一个程序往往由多个源文件组成,那么构成一个程序的各个相对独立的源文件通常称为模块。这样把一个程序分成多个功能相对独立的程序模块分别编制、调试后,再用连接程序把它们连接在一起生成一个完整的程序的设计方法称为模块化程序设计。模块化程序设计的优点1.开发速度快2.可维护性与可读性强3.可移埴性强模块划分的原则和方法模块划分是一个自上而下的过程。主模块是一个总控模块,首先确定主要的模块,也就是说,要把总任务划分成几个主要的子任务。一般来说,可以分成输入任务、输出任务和一个

温馨提示

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

评论

0/150

提交评论