模块化程序设计与混合编程.ppt

上传人:小飞机 文档编号:6439036 上传时间:2023-10-31 格式:PPT 页数:80 大小:660.50KB
返回 下载 相关 举报
模块化程序设计与混合编程.ppt_第1页
第1页 / 共80页
模块化程序设计与混合编程.ppt_第2页
第2页 / 共80页
模块化程序设计与混合编程.ppt_第3页
第3页 / 共80页
模块化程序设计与混合编程.ppt_第4页
第4页 / 共80页
模块化程序设计与混合编程.ppt_第5页
第5页 / 共80页
点击查看更多>>
资源描述

《模块化程序设计与混合编程.ppt》由会员分享,可在线阅读,更多相关《模块化程序设计与混合编程.ppt(80页珍藏版)》请在三一办公上搜索。

1、高等教育出版社,王成端 主编,2008.6,汇编语言程序设计(第2版),普通高等教育“十一五”国家级规划教材,第9章 模块化程序设计与混合编程,模块化技术是大型软件开发的基本技术,高、低级语言间的混合编程是大型软件常用的另一技术。本章详述这两种技术,应重点掌握二个汇编语言模块间的代码调用与数据交换方法,以及编写能够被C语言调用的汇编语言模块的方法。,9.1 模块化程序设计 9.2 汇编语言与高级语言的接口实训一 键盘录入数据的转换与显示实训二 C语言调用汇编语言子程序进行数据传递与显示,9.1 模块化的程序设计,9.1.1 模块化设计原则9.1.2 模块之间组合与通信9.1.3 模块化设计举例

2、,第9章 模块化程序设计与混合编程,对于一个大而复杂的任务,我们一般把整个程序分成若干个子任务或模块,称为“模块化程序设计”。模块化设计的难题是:如何把程序分成模块,以及如何再装配起来。,1.模块化的优点,单个的模块易于编写、调试、修改。一个模块可以在多个地方或多个程序中使用。可以直接利用已有的模块。多个程序员可以并行工作。,9.1.1 模块化设计原则,第9章 模块化程序设计与混合编程,2.模块化的缺点,增加了模块的划分工作。模块的装配也是难题,尤其是合并多人编写的程序时。为调试一个模块,需要额外地为其准备调试数据、专用程序等,模块化程序设计的关键是模块的划分,层次图和模块说明是模块划分的主要

3、描述工具。层次图是一个方块图,用来表示模块与模块之间的调用关系,如图9-1所示。模块说明是模块功能的文字描述,应简要写出模块的功能、所用基本算法、入口参数、出口参数、所调用到的其它模块、调用注意事项等信息。,3.模块的描述,第9章 模块化程序设计与混合编程,图9-1 层次图,第9章 模块化程序设计与混合编程,4.模块的划分原则,模块的划分无固定的规则,应视情况灵活确定,但应遵循如下基本原则:模块间的调用关系尽可能简单。尽量避免从多个入口点进入模块或从多个出口点退出,否则模块不易调试。模块间的数据耦合应尽量小,即尽量减少数据传递,对于需要传递参数个数较多的模块,可考虑合并成一个模块。模块的大小适

4、中,若一个模块太大,则编写调试因难;若太小,模块间的调用、连接开销则太大,一般一个模块长度在50至500行内为宜。,返回本章首页,返回本节首页,第9章 模块化程序设计与混合编程,模块之间的组合与通讯,一个大程序划分为若干模块后,各个模块分别编写并汇编成目标模块文件(.OBJ文件),然后由链接程序(如LINK.EXE或TLINK.EXE等)将各个模块组合在一起,成为一个可执行文件(.EXE文件)。模块间的组合,主要由链接程序完成。一个模块中往往有多个段,多个模块则有更多的段,这些段在链接时有的需要合并成一个,有的则需要相互重叠,有的则不与其它段发生联系。要想使链接程序按预想的方案组合各个段,关键

5、是在各个模块编写前约定好段名、组合类型、定位类型、类别等参数,其中组合类型指定各个模块间的同名段该如何处理(各自独立?相互合并?相互重叠?),而定位类型决定段的地址要求,它关系到段间是否留有空隙的问题,类别则会导至调整段的先后次序。其中主要的参数是组合类型。,返回本章首页,返回本节首页,第9章 模块化程序设计与混合编程,由于各个模块被单独汇编,所以A模块要想调用B模块内定义的符号(变量、过程等),那么A模块必须通知汇编程序指定的符号在别的模块内,否则汇编时会出错。另外,B模块内也必须通知汇编程序某个符号将被其它模块调用,否则汇编程序不会把它写入目标文件中,最后导致链接失败。,第9章 模块化程序

6、设计与混合编程,9.1.3 模块化设计举例,1.模块间代码的转移 一个模块调用另一个模块的代码时,若调用者所在的段与位于另一模块内的被调用者所在的段链接后能合并成一个段,则调用相当于段内调用,转移采用近转移即可;若不能合并成一个段,则必须采用远转移。,第9章 模块化程序设计与混合编程,【例9.1】某系统由A、B二个模块构成,A模块要调用到B模块的一个过程disp,以显示一个字符。二个模块如下:,模块A:,EXTRNDISP:NEAR.MODELSMALL.CODESTART:CALLDISPMOVAH,4CHINT21HENDSTART,第9章 模块化程序设计与混合编程,模块B:,PUBLIC

7、DISP.MODELSMALL.CODEDISPPROCNEARMOVDL,AMOVAH,2INT21HRETDISPENDPEND,第9章 模块化程序设计与混合编程,假设模块A存盘后文件名为A.ASM,模块B存盘后文件名为B.ASM,则可用以下命令行分别汇编,然后链接合并。MASM A;MASM B;LINK A+B;最后得到可执行文件A.EXE。执行该文件,可在屏幕上显示字符A。在这个例子中,两个模块存储模式相同,并且都是用.CODE伪指令指定代码段,则二个模块内代码段的段名、定位类型、组合类型、类别等都由汇编程序自动生成(分别为:TEXT、WORD、PUBLIC、CODE),由于二个段是

8、同名段,且都为PUBLIC组合类型,故链接程序最后将其合并为一个段,因而模块A对模块B内disp的调用为段内调用。,第9章 模块化程序设计与混合编程,【例9.2】段间调用。对例9-1中的二个模块作如下修改:,EXTRNDISP:FAR.MODELSMALL.CODESTART:CALLDISPMOVAH,4CHINT21HENDSTART,模块A:,模块B:,PUBLICDISPCODESEGMENTASSUMECS:CODEDISPPROCFARMOVDL,AMOVAH,2INT21HRETDISPENDPEND,第9章 模块化程序设计与混合编程,在这个例子中,由于模块B中代码段的段名与模块

9、A中的不再一致,故二个段是各自独立的代码段,调用成为段间调用,disp不论在模块A中的外部符号声明还是在模块B中的类型定义,都必须为FAR类型。,第9章 模块化程序设计与混合编程,2.模块间数据的访问 两个模块之间若要相互交换数据,可用如下几种办法实现:a 通过寄存器传递 b 通过堆栈传递 c 互访对方变量或数据区 a、b二种方法较为简单,因为不论在同一模块内的各个过程间,还是在不同模块内的各个过程间,都中在共用相同的CPU寄存器和堆栈段,所以这种况下调用在模块内还是在模块外情况并无差别。下面主要讨论模块间互访对方变量或数据区的方法。,第9章 模块化程序设计与混合编程,3.模块间互访对方变量或

10、数据区的方法 一个模块要访问另一个模块内的数据,关键是调整数据段寄存器使之存储被访问数据所在段的段地址。,【例9.3】演示一个模块内访问另一模块内变量或数据区的方法。某工程由如下二个模块组成。,第9章 模块化程序设计与混合编程,模块A:,EXTRNSET:FARPUBLICBUF1.MODELSMALL.DATABUF1DB3.CODESTART:MOVAX,DATAMOVDS,AXCALLSETMOVDL,BUF1MOVAH,2INT21HMOVAH,4CHINT21HENDSTART,第9章 模块化程序设计与混合编程,模块B:,EXTRNBUF1:BYTEPUBLICSETCODESEGM

11、ENTASSUME CS:CODESETPROCFARMOVBUF1,8RETSETENDPCODEENDSEND,在这个例子中,当控制从模块A转到模块B后,DS寄存器仍指向模块A的数据段,所以模块B中可用DS直接访问模块A中的数据,这种情况相当于在一个模块中有二个代码段一个数据段的情况,不论控制在哪个代码段内,都可直接访问数据段。,第9章 模块化程序设计与混合编程,【例9.4】二个模块都有自己的数据段时从一个模块访问另一个模块数据区的方法。对例9.3进行修改如下:,EXTRNSET:FARPUBLICBUF1.MODELSMALL.DATABUF1DB3.CODESTART:MOVAX,DA

12、TAMOVDS,AXCALLSETMOVDL,BUF1MOVAH,2INT21HMOVAH,4CHINT21HENDSTART,模块A:,第9章 模块化程序设计与混合编程,模块B:,EXTRNBUF1:BYTEPUBLICSETDATASEGMENTBUF2DB5DATAENDSCODESEGMENTASSUME CS:CODE,DS:DATASETPROCFARPUSHDSMOVAX,DATAMOVDS,AXMOVBL,BUF2MOVAX,SEG BUF1MOVDS,AXMOVAL,BUF1ADDAL,BLMOVBUF1,ALPOPDSRETSETENDPCODEENDSEND,运行显示:8

13、,第9章 模块化程序设计与混合编程,这个例子中模块A与模块B各有自已的数据段,当控制从模块A转移到模块B时,DS仍指向模块A的数据段,由于模块B要使用DS访问自已的数据段,所以它先将DS压栈保存,然后使DS指向自己的数据段,完成数据的读取后,又修改DS使之指向模块A的数据段,从而访问模块A的BUF1变量。最后,在返回模块A前通过出栈将DS恢复为进入时的初值(本例中由于在访问模块A的变量BUF1时已使DS指向了模块A的数据段,保存恢复DS的工作可以省略)。,这个例子的关键是访问外部模块的变量前事先要调整DS使之保存外部变量所在段的段地址。由于访问内存并不一定非要用DS,若用其它段寄存器(如ES)

14、,则除了要事先调整该段寄存器使之存储被访问外部变量所在段的段值外,还要在使用变量时在前面加上段超越标记,如可将模块B修改如下:,第9章 模块化程序设计与混合编程,EXTRNBUF1:BYTEPUBLICSETDATASEGMENTBUF2DB5DATAENDSCODESEGMENTASSUME CS:CODE,DS:DATASETPROCFARPUSHDSMOVAX,DATAMOVDS,AXMOVBL,BUF2MOVAX,SEG BUF1MOVES,AXMOVAL,ES:BUF1;使用段超越标记ADDAL,BLMOVES:BUF1,ALPOPDSRETSETENDPCODEENDSEND,第9

15、章 模块化程序设计与混合编程,模块间信息传递的另一个方法是利用COMMON段,只要二个模块中都定义一个同名同类别的数据段,且组合类型都为COMMON,由于这二个段在链接时要重合为一个段,只要在这二个段内将变量安排在相同的位置上,那么它们对应的是相同的存储区。,【例9.5】利用COMMON段访问另一个模块的数据区。将上例进行修改,如下:,第9章 模块化程序设计与混合编程,模块A:,EXTRNSET:FARDATASEGMENTCOMMONBUF1DB3BUF2DB0DATAENDSCODE1 SEGMENTASSUMECS:CODE1,DS:DATASTART:MOVAX,DATAMOVDS,A

16、XCALLSETMOVDL,BUF2MOVAH,2INT21HMOVAH,4CHINT21HCODE1ENDSENDSTART,第9章 模块化程序设计与混合编程,模块B:,PUBLICSETDATASEGMENTBUF1DB?BUF2DB?DATAENDSCODE2 SEGMENTASSUMECS:CODE2,DS:DATASETPROCFARMOVAL,BUF1ADDBUF2,ALRETSETENDPCODE2ENDSEND,返回本章首页,返回本节首页,第9章 模块化程序设计与混合编程,9.2 汇编语言与高级语言的接口,9.2.1 概述9.2.2 嵌入式汇编9.2.3 汇编语言与C语言的接口

17、,第9章 模块化程序设计与混合编程,概述,对于一项软件工程而言,首要目标是保证达到软件所需质量,其次是尽可能降低开发成本。汇编语言与高级语言在语言表达能力、表达的方便程度、编程效率和运行效率方面各有特点,这就需要它们相互“取长补短”,即混合编程,以便取得最大的综合效益。高级语言与汇编语言的混合编程通常发生在下列情况下:需要访问机器的硬件特征,这些特征用高级语言表达比较困难。某些程序段需要频烦运行,单次运行速度的提高可显著提高整个系统运行效率。有现成的汇编语言程序段可用。,第9章 模块化程序设计与混合编程,高级语言种类较多,本章主要讨论与C语言的混合编程问题,流行的C编译器主要有Turbo C和

18、MS C,本书主要以Turbo C为例。C语言的目标是追求效率,因而它与汇编语言与较多的“血源关系”。C语言提供了三种调用汇编语言功能的方法:(1)嵌入汇编(2)寄存器伪变量与bdos()、intdos()、int86()等系列DOS、BIOS服务调用函数(3)C语言与汇编语言的混合编程。其中第二种方法因主要涉及C语言的编程问题,本书不作讨论,请读者参考相关C语言书籍。,返回本章首页,返回上一节,返回本节首页,第9章 模块化程序设计与混合编程,嵌入式汇编 在C程序中可以直接插入汇编语言语句,称为嵌入式汇编。,1.嵌入式汇编语句的格式,asm,说明:,asm 是嵌入汇编语句的关键字,可是任何有效

19、的汇编指令或伪指令,是可选的。,是汇编指令或伪指令的操作数,它可引用C语言中的常量、变量和标号。,都表示asm语句的结束,一行内可写多条嵌入式汇编语句,它们以“;”分隔。一行内如果只有一个asm语句,则后面不需要“;”,asm 语句是C语言中唯一的依靠换行的语句。,asm语句如需要注释,必须采用C格式的注释,即“/*注释内容*/”,编译后,函数外的汇编语句放在数据段内,函数内的汇编语句放在代码段内。,第9章 模块化程序设计与混合编程,2.嵌入式汇编的编译连接,含有嵌入式汇编的程序不能在Turbo C集成开发环境下进行编译,因为集成开发环境不支持这一功能。这样的程序必须使用Turbo C的命令行

20、编译器TCC.EXE进行编译,而TCC.EXE在编译是又要调用TASM.EXE,所以当前目录下必须保证有汇编程序TASM.EXE。,TCC.EXE调用格式为:,其中files需要编译或连接的文件,可有一个或多个。options 为各个参数选项,常用的有“-B”用于指示源程序中使用了嵌入汇编,“S”能使TCC.EXE编译成汇编语言的源程序输出。若程序中使用了嵌入汇编,调用TCC时必须选择-B参数,或者是在程序中使用伪指令“#pragma inline”,此伪指令功能与“-B”参数等效。,TCC options files,第9章 模块化程序设计与混合编程,【例9.6】嵌入式汇编的编译连接有如下程

21、序:asm BUF DB“this is a test$”main()asmMOVAH,9asmMOVDX,OFFSETBUFasmINT21H 在TC的集成开发环境下编辑完以上程序后,存盘(假设文件名为 test.c),然后以如下命令行进行编译连接:tcc B test.c.(注意:tcc的参数是区分大小写的,不要将“-B”写成“-b”)则可生成test.exe文件,在DOS下运行之,即可在屏幕上显示“this is a test”。,第9章 模块化程序设计与混合编程,【例9.7】从嵌入汇编语句中访问C定义的局部及全局变量。分析如下程序:,#includeinti=3;main()charj

22、=a;structsintx;inty;stru;stru.x=50;stru.y=20;printf(nj=%c,j);asmincbyteptrjprintf(nj=%c,j);printf(ni=%d,i);asmmovax,stru.xasmsubax,stru.yasmmovi,axprintf(ni=%d,i);,该程序的运行结果如下:,第9章 模块化程序设计与混合编程,3.嵌入汇编的限制,嵌入汇编码中的转移指令只能引用C中定义的标号,而嵌入汇编码中的非转移指令则可引用除标号以外的任何C元素。在嵌入汇编码的末尾,下列寄存器的值必须与进入嵌入汇编码时的值保持一致:BP、SP、CS、D

23、S、SS。如果C程序中用到了寄存器变量,还需再保证SI、DI的值不变,其它寄存器的值可自由改变。,返回本章首页,返回本节首页,第9章 模块化程序设计与混合编程,9.2.3 汇编语言与语言的混合编程,1.调用约定,在混合编程中,双方在如下方面必须遵循相同的接口约定,才可使双方的代码协同工作。,内存模式:,即对内存的使用,主要是段、组的划分,以及段名、组名的约定等。若二种语言对段的划分情况不同,则双方指针或地址的使用便不同,这时极易发生运行错误。如在函数调用中,一方将四节地址压栈,而另一方则只将二字节地址出栈,可能造成程序崩溃。,命名约定:,即标识符的命名规则,以及是否区分大小写等。,调用约定:,

24、调用者以什么顺序将参数压栈,被调用者以什么顺序提取参数,由谁负责最后清除堆栈中的参数等。,参数传递约定:,调用参数以什么方式传递的,例如传递的是参数的值还是参数的地址等。,第9章 模块化程序设计与混合编程,2.Tucbo C的六种存储模式 为允许程序员决定内存布局结构,以便更合理高效地使用内存,Tucbo C提供了六种存储模式(MS C相同),如下:,极小模式:,程序的所有代码和数据限制在64K内,即程序只有一个段,CS=DS=ES=SS,所有指针全部为near指针。,小模式:,代码和数据各占一个段,因而代码和数据分别不能超过64K。DS=SS=ES,所有指针都为near指针。,中模式:,代码

25、段用far指针,数据段用near指针,代码段可多达1M,数据不能超过64K。,紧凑模式:,数据段用far指针,代码段用near指针。代码最多64K,数据可多达1M。,大模式:,代码、数据均用far指针,因而均可多达1M。,极大模式:,代码、数据均用far指针。Turbo C一般限制静态数据为64K,极大模式下可打破此限制。,第9章 模块化程序设计与混合编程,3.Turbo C的调用约定 为了解Turbo C的调用接口机制,观察其以汇编形式给出的编译结果是最好的方式。假设有如下C程序:,#includeintpara1=10;intpara2;intadd(inta,intb)return(a+

26、b);main()para1+;para2=add(para1,20);printf(n%dn%d,para1,para2);,以上程序,设存盘后文件名为test.c,则可用如下命令行编译成汇编语言结果输出:,TCC-ms-Stest.c,第9章 模块化程序设计与混合编程,输出结果为(为便于理解,添加了中文注释):,_TEXTsegmentbytepublicCODE;代码段DGROUPgroup_DATA,_BSSassumecs:_TEXT,ds:DGROUP,ss:DGROUP_TEXTends_DATAsegmentwordpublicDATA;数据段_para1labelworddw

27、10_DATAends_TEXTsegmentbytepublicCODE;代码段;?debugL5;注明以下代码是由源程序的第5行产生,下同,第9章 模块化程序设计与混合编程,_addprocnear;函数addpushbpmovbp,sp;?debugL7movax,wordptrbp+4;取第一个参数addax,wordptrbp+6;取第二个参数,并加;到在AX内的第一个参数上jmpshort11:;?debugL8popbpret_addendp;?debugL10,第9章 模块化程序设计与混合编程,_mainprocnear;主函数;?debugL12incwordptrDGROU

28、P:_para1;语句para1+;?debugL13movax,20pushax;为调用add压栈第二个参数(最右参数)pushwordptrDGROUP:_para1;为调用add压栈第一个参数callnearptr_add;调用add函数popcxpopcx;以上两句清除堆栈中的二个参数movwordptrDGROUP:_para2,ax;add返回值在AX;中,将其赋给para2;?debugL14pushwordptrDGROUP:_para2,第9章 模块化程序设计与混合编程,;为调用printf压栈最右参数pushwordptrDGROUP:_para1;为调用printf压栈第

29、二个参数movax,offsetDGROUP:spushax;为调用printf压栈第一个参数(最左参数)callnearptr_printf;调用printfaddsp,6;清除堆栈中的三个参数2:;?debugL15ret_mainendp_TEXTends,第9章 模块化程序设计与混合编程,_BSSsegmentwordpublicBSS;未初始化的数据段_para2labelworddb2dup(?)_BSSends?debugCE9_DATAsegmentwordpublicDATA;数据段slabelbyte;定义字符串“n%dn%d”db10db37db100db10db37db

30、100db0_DATAends,第9章 模块化程序设计与混合编程,_TEXTsegmentbytepublicCODE;代码段extrn_printf:near;声明外部函数_TEXTendspublic_main;声明公共标识符public_addpublic_para2public_para1end,第9章 模块化程序设计与混合编程,说明:,(1)汇编模块要遵循与Turbo C一致的内存模式,同时采用Turbo C的段名约定,这可用二种方法实现:,使用简化的段定义方式,只要用伪指令.MODEL说明内存模式为SMALL,然后用伪指令.CODE定义代码段,用伪指令.DATA定义数据段,则生成的

31、段名与Turbo C的段名一致。,先编写一个只有空架的C程序,用Turbo C的-B参数编译生成汇编输出,即得到了完整的程序框架。,(2)Turbo C在编译时会自动在全局变量及函数名前加上下划线,如果汇编模块中的函数或变量要供Turbo C访问,变量名或函数名也必须以下划线开头。另外C语言大小写敏感,而汇编语言大小写不敏感,为不致命名混淆,应使汇编语言也保持大小写敏感,汇编程序的命令行选项/ml(所有符号区分大小写)和/mx(仅公共标识符和外部标识符区分大小写)可做到这一点。,第9章 模块化程序设计与混合编程,图9-2 C函数调用中的堆栈结构,(3)C语言的参数均通过堆栈传递,且自右向左压栈

32、。当程序进入被调函数时,堆栈结果如图9-2 a所示:,C语言的被调函数是通过BP间址的方式访问堆栈的,为不破坏BP的值,在使用BP前将其压栈,这时各参数的位置如图9-2 b所示。,第9章 模块化程序设计与混合编程,C语言中基本数据类型(char、short int、int、long int、unsigned int、float、double)以及结构类型的参数都是以传值方式传递的,而数组类型的参数是传址方式传递的,而有些参数要先进行类型转换后再压栈传递(如char先转换为int)。基本情况如表9-1所示:,注:若函数调用是远调用,则栈顶部的返回地址为4字节,各参数的偏移值应相应修改。,第9章

33、模块化程序设计与混合编程,表9-1 C语言参数的传递方式,第9章 模块化程序设计与混合编程,(4)被调函数的返回值通常放在AX(16位)或DX:AX(32位)内,但若是float、double、struct、union类型,则返回值放入静态数据区,然后返回其指针。,(5)被调函数要保护的寄存器,同嵌入汇编中对寄存器的限制情况相同。,第9章 模块化程序设计与混合编程,【例9.8】编写一个能按用户指定的内存地址及长度以十六进制格式显示内存单元内容的程序。分析:从键盘接收用户输入的内存地址及内存长度的工作可由C语言完成,因为C语言的库函数printf()及scanf()在给出输入提示,读取用户输入方

34、面比较方便。对于读取指定地址处的数据,汇编语言则是最方便的,故这部分工作由一个汇编模块完成。整个程序如下:,C语言模块:,第9章 模块化程序设计与混合编程,#includeexternvoidmove(char*,unsigned int,unsigned int,unsigned int);main()unsignedint segment,offset,number;charbuf20;inti;printf(nPlease input the address(segment:offset):);scanf(%x:%x,&segment,&offset);printf(nPlesae in

35、put the number(less than 20):);scanf(%u,&number);move(buf,number,offset,segment);printf(n);for(i=0;inumber;i+)printf(%2x,bufi);(本模块在编译时用small模式),第9章 模块化程序设计与混合编程,汇编语言模块:.MODELSMALL.CODEPUBLIC_move_movePROCPUSHBPMOVBP,SPPUSHSIPUSHDIPUSHDSMOVDI,BP+4MOVCX,BP+6LDSSI,BP+8CLDREPMOVSBPOPDSPOPDIPOPSIPOPBP_m

36、oveENDPEND,第9章 模块化程序设计与混合编程,在上程序中,C语言部分先提示用户输入要显示内存的段地址和偏移量值,然后读取这二个数(以十六进制方式读取,因为一般在表示地址时习慣用这种进制)赋给变量segment和offset。其次,提示用户输入要显示的内存单元的个数(为简单起见,不超过20个),然后将这一数据读到变量number中。最后,调用汇编函数_move将用户指定内存的内容传送到位于main函数内的局部变量buf中。_move 函数四个参数的含义依次为:接收数据的目标地址,要传送的内存单元个数,要传送的内存起始位置的偏移量和段地址。在收到_move函数传来的数据后,main()函

37、数再调用printf()库函数将这些数据以十六进制显示在屏幕上。,第9章 模块化程序设计与混合编程,程序运行结果如下(画线部分为键入内容):,第9章 模块化程序设计与混合编程,从输出结果可以看出,有些内存单元的内容显示有误,因为是按字节显示的内存内容,一个数据应不大于FFH才对,而有些单元显示的却是四位十六进制数,错在哪儿呢?让我们分析一下C语言模块在编译后生成的部分汇编代码,其中语句for(i=0;inumber;i+)printf(%2x,bufi);对应的代码如下:,第9章 模块化程序设计与混合编程,xorsi,si;完成i=0jmpshort54:moval,byteptrbp+si-

38、20;取出bufi的内容,取一个字节,这是正确的cbw;将AL内容符号扩展为一个字(这是导致错误处)pushax;调用printf所需的第一个参数压栈movax,offset DGROUP:s+83pushax;调用printf所需的第二个参数压栈callnearptr _printfpopcxpopcx;清除堆栈中的废参数3:incsi;i+5:cmpsi,wordptr bp-22;inumber?jb4,第9章 模块化程序设计与混合编程,显然,原因在于因为压栈只能按字进行,AL内的待显示内容被CBW语句符号扩展为一个字,然后通过堆栈传递给了printf函数,而printf并不知道原参数是

39、一个字还是一个字节,它按一个字进行了显示,若AL内的二进制数最高位1,则符号扩展后AH内为FFH,所以大于80H的数均在前面加显示了FFH。在C语言条件下显然无法改正上述错误,我们将数据的显示工作移到汇编代码内进行,就很容易按我们的要求进行显示了。改正后的程序详见实训二。,返回本章首页,返回上一小节,返回本节首页,第9章 模块化程序设计与混合编程,实训一 键盘录入数据的转换与显示,实训内容:编一程序,能从键盘读入二个五位十进制数(1位符号位+4位数值位),并将这二个十进制数分别转换为二进制数,然后求其和,再将和以十进制形式进行显示。分析:这个程序主要练习十进制数与二进制数之间的相互转换。由于用

40、户输入的数一般是用十进制表示的,而这些数在计算机内存储、运算时一般都用二进制,这就涉及到了十进制数向二进制数的转换问题;当计算结果需要显示或打印时,又要将二进制数转换为十进制数,这又涉及到二进制数如何转换为十进制数。由于在计算机内这种转换经常进行,所以二进制数与十进制数之间的转换算法是汇编语言程序员应掌握的基本算法之一。,第9章 模块化程序设计与混合编程,由于程序较大,我们将其分在二个模块内,模块A负责十制数的读取、二个数的求和,以及和的显示工作,模块B负责将十进制数转换为二进制,以及将二进制数转换为十进制数。由于二个模块内数据传递关系比较复杂,所以采用COMMON段可避免在模块间传递参数,使

41、源程序结构清析。另外,为突出主要算法,减少不必要的细节考虑,将用户输入的数据限制在4位(不含符号位)以内,这样数值在-9999至+9999范围以内,转换为二进制后在16位补码的表示范围内,而其相加后的和也在16位补码的表示范围内;同时,在将二进制数转换为十进制数显示时,也只须考虑万位及以下数字的转换即可,不存在十万位以上的数字。参考程序:,第9章 模块化程序设计与混合编程,模块A:EXTRNASC_TO_BIN:FAR,BIN_TO_ASCII:FARDATASEGMENTCOMMONINPUT_MESSAGE DB0AH,DB PLEASE INPUT A NUMBER(LESS THAN

42、5 FIGURES):$IN_ASC_BUFDB6;十进制数的输入缓冲区,共可接收6个字符DB?;保留,用于10号调用时DOS填入实际输入字符个数DB6 DUP(?);一个符号位,四位数字ASCII码,加上一个回车;符,共计6字符BIN_BUF1DW?;将第一个数转换为二进制后,放于此处OUTPUT_MESSAGEDB0AH,THE SUMIS:,$OUT_ASC_SUMDB6 DUP(?),$;将二个数的和转换为ASCII码后,放于此处,以供9号调用显示DATAENDS,第9章 模块化程序设计与混合编程,CODESEGMENTASSUMECS:CODE,DS:DATASTART:MOVAX,

43、DATAMOVDS,AXMOVDX,OFFSETINPUT_MESSAGEMOVAH,9INT21H;提示输入一个数MOVDX,OFFSETIN_ASC_BUFMOVAH,10INT21H;读取第一个数CALLASC_TO_BIN;转换第一个数为二进制MOVBIN_BUF1,AX;保存第一个数MOVDX,OFFSETINPUT_MESSAGEMOVAH,9INT21H;提示输入一个数,第9章 模块化程序设计与混合编程,MOVDX,OFFSETIN_ASC_BUFMOVAH,10INT21H;读取第二个数CALLASC_TO_BIN;转换第二个数为二进制ADDAX,BIN_BUF1;计算这二个数

44、之和CALLBIN_TO_ASCII;将和转换为ASCII码,以供显示用MOVDX,OFFSETOUTPUT_MESSAGEMOVAH,9INT21HMOVDX,OFFSETOUT_ASC_SUMMOVAH,9INT21HMOVAH,4CHINT21HCODEENDSENDSTART,第9章 模块化程序设计与混合编程,模块B:PUBLICASC_TO_BIN,BIN_TO_ASCIIDATASEGMENTCOMMONINPUT_MESSAGEDB 0AH,DB PLEASE INPUT A NUMBER(LESS THAN 5 FIGURES):$IN_ASC_BUFDB 6;十进制数的输入缓

45、冲区,共可接收6个字符DB?;保留,用于10号调用时DOS填入实际输入字符个数DB 6 DUP(?);一个符号位,四位数字ASCII码,加上一个回车符,共计6字符BIN_BUF1DW?;将第一个数转换为二进制后,放于此处OUTPUT_MESSAGEDB 0AH,THE SUM IS:,$OUT_ASC_SUMDB6 DUP(?),$;将二个数的和转换为ASCII码后,放于此处,以供9号调用显示DATAENDSCSEGSEGMENTASSUMECS:CSEG,DS:DATAASC_TO_BINPROC FAR,第9章 模块化程序设计与混合编程,;ASCII码转换为二进制数;入口:十进制数的ASC

46、II码在IN_ASC_BUF内;出口:转换后的二进制数在AX内;算法:先将其转换成十进制数字,再用累加和乘10加X;的方法变成二进制数,如将;358转换为二进制数,可先将累加和赋0,再计算;(0*10+3)*10+5)*10+8),结果为二;进制数,再由符号位决定是否需要求补。MOVCL,IN_ASC_BUF+1;取字符个数MOVCH,0DECCL;扣除符号位MOVBX,OFFSETIN_ASC_BUF+3;调整BX指向十进制数的最高位PUSHBXPUSHCX,第9章 模块化程序设计与混合编程,L1:MOVAL,BXANDAL,0FHMOVBX,ALINCBXLOOPL1;将所有数字字符的高四

47、位清0,使之变为数字值POPCXPOPBXMOVAX,0;累加和赋初值MOVSI,10,第9章 模块化程序设计与混合编程,L2:MULSIADDAL,BXADCAH,0INCBXLOOPL2;累加乘10CMPIN_ASC_BUF+2,+JZL3;符号是正号,转移NEGAX;符号是负号,求补L3:RETASC_TO_BINENDP,第9章 模块化程序设计与混合编程,BIN_TO_ASCIIPROCFAR;将二进制数转换为对应十进制数数字的ASCII码;入口:二进制数在AX内;出口:转换后的ASCII码在OUT_ASC_SUM变量内;算法:AX中的数范围在+32767到-32768之间,先检查AX

48、中;的符号位,以决定输出“+”;还是“-”,若是负数,应先求补,得到原码后即可与正数作;统一处理。转换方法为将被转换;的二进制数先除以10000,商;即为万位数,再将余数除以;1000,商为千位数,以此类推,;求出百、十位数,剩下的为个位数。最后,将各个数加上;30H,即成为对应字符。MOVOUT_ASC_SUM,+CMPAX,0JGEL4;不是负数,转移NEGAXMOVOUT_ASC_SUM,-,第9章 模块化程序设计与混合编程,L4:CWDMOVBX,10000DIVBXADDAL,30H;将万位转换为数字(商应在AX内,但因为商不大于;3,所以有效部分在AL内)MOVOUT_ASC_SU

49、M+1,AL;保存万位数字MOVAX,DX;将余数置入AX内,以便当作被除数CWDMOVBX,1000DIVBXADDAL,30HMOVOUT_ASC_SUM+2,AL;保存千位数字MOVAX,DX;将余数置入AX内,以便当作被除数MOVBL,100DIVBLADDAL,30H,第9章 模块化程序设计与混合编程,MOVOUT_ASC_SUM+3,AL;保存百位数字MOVAL,AHCBWMOVBL,10DIVBLADDAL,30HMOVOUT_ASC_SUM+4,AL;保存十位数字ADDAH,30HMOVOUT_ASC_SUM+5,AH;保存个位数字RETBIN_TO_ASCIIENDPCSEG

50、ENDSEND,第9章 模块化程序设计与混合编程,程序运行结果如下(画线部分为键入内容):,返回本章首页,第9章 模块化程序设计与混合编程,实训二 C语言调用汇编语言子程序进行数据传递与显示,实训内容:同本章例9.8,但要克服例9.8中的显示缺陷 分析:因为在printf()中控制一个字节数据的显示格式比较困难,故我们将显示部分也在汇编模块中完成即可。参考程序:汇编程序:.MODELSMALL.CODEPUBLIC_disp;声明全局符号,以便C模块调用CHAR_DISPLAY PROC;将AL内“0000XXXX”格式的数据以十六进制形式显示出来(一个字符);入口参数:在AL内(AL的高四位

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号