模块化程序设计与混合编程_第1页
模块化程序设计与混合编程_第2页
模块化程序设计与混合编程_第3页
模块化程序设计与混合编程_第4页
模块化程序设计与混合编程_第5页
已阅读5页,还剩75页未读 继续免费阅读

下载本文档

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

文档简介

第一页,共八十页,2022年,8月28日第9章模块化程序设计与混合编程模块化技术是大型软件开发的基本技术,高、低级语言间的混合编程是大型软件常用的另一技术。本章详述这两种技术,应重点掌握二个汇编语言模块间的代码调用与数据交换方法,以及编写能够被C语言调用的汇编语言模块的方法。9.1模块化程序设计9.2汇编语言与高级语言的接口实训一键盘录入数据的转换与显示实训二C语言调用汇编语言子程序进行数据传递与显示第二页,共八十页,2022年,8月28日9.1模块化的程序设计9.1.1模块化设计原则9.1.2模块之间组合与通信9.1.3模块化设计举例第9章模块化程序设计与混合编程第三页,共八十页,2022年,8月28日对于一个大而复杂的任务,我们一般把整个程序分成若干个子任务或模块,称为“模块化程序设计”。模块化设计的难题是:如何把程序分成模块,以及如何再装配起来。1.模块化的优点单个的模块易于编写、调试、修改。一个模块可以在多个地方或多个程序中使用。可以直接利用已有的模块。多个程序员可以并行工作。9.1.1模块化设计原则第9章模块化程序设计与混合编程第四页,共八十页,2022年,8月28日2.模块化的缺点增加了模块的划分工作。模块的装配也是难题,尤其是合并多人编写的程序时。为调试一个模块,需要额外地为其准备调试数据、专用程序等模块化程序设计的关键是模块的划分,层次图和模块说明是模块划分的主要描述工具。层次图是一个方块图,用来表示模块与模块之间的调用关系,如图9-1所示。模块说明是模块功能的文字描述,应简要写出模块的功能、所用基本算法、入口参数、出口参数、所调用到的其它模块、调用注意事项等信息。3.模块的描述第9章模块化程序设计与混合编程第五页,共八十页,2022年,8月28日图9-1层次图

主控模块

模块A

模块B

模块C

模块E

模块F

模块G

模块H

模块D

第9章模块化程序设计与混合编程第六页,共八十页,2022年,8月28日4.模块的划分原则模块的划分无固定的规则,应视情况灵活确定,但应遵循如下基本原则:

①模块间的调用关系尽可能简单。尽量避免从多个入口点进入模块或从多个出口点退出,否则模块不易调试。

②模块间的数据耦合应尽量小,即尽量减少数据传递,对于需要传递参数个数较多的模块,可考虑合并成一个模块。

③模块的大小适中,若一个模块太大,则编写调试因难;若太小,模块间的调用、连接开销则太大,一般一个模块长度在50至500行内为宜。返回本章首页返回本节首页第9章模块化程序设计与混合编程第七页,共八十页,2022年,8月28日9.1.2模块之间的组合与通讯一个大程序划分为若干模块后,各个模块分别编写并汇编成目标模块文件(.OBJ文件),然后由链接程序(如LINK.EXE或TLINK.EXE等)将各个模块组合在一起,成为一个可执行文件(.EXE文件)。模块间的组合,主要由链接程序完成。一个模块中往往有多个段,多个模块则有更多的段,这些段在链接时有的需要合并成一个,有的则需要相互重叠,有的则不与其它段发生联系。要想使链接程序按预想的方案组合各个段,关键是在各个模块编写前约定好段名、组合类型、定位类型、类别等参数,其中组合类型指定各个模块间的同名段该如何处理(各自独立?相互合并?相互重叠?),而定位类型决定段的地址要求,它关系到段间是否留有空隙的问题,类别则会导至调整段的先后次序。其中主要的参数是组合类型。

返回本章首页返回本节首页第9章模块化程序设计与混合编程第八页,共八十页,2022年,8月28日由于各个模块被单独汇编,所以A模块要想调用B模块内定义的符号(变量、过程等),那么A模块必须通知汇编程序指定的符号在别的模块内,否则汇编时会出错。另外,B模块内也必须通知汇编程序某个符号将被其它模块调用,否则汇编程序不会把它写入目标文件中,最后导致链接失败。第9章模块化程序设计与混合编程第九页,共八十页,2022年,8月28日9.1.3模块化设计举例1.模块间代码的转移

一个模块调用另一个模块的代码时,若调用者所在的段与位于另一模块内的被调用者所在的段链接后能合并成一个段,则调用相当于段内调用,转移采用近转移即可;若不能合并成一个段,则必须采用远转移。第9章模块化程序设计与混合编程第十页,共八十页,2022年,8月28日【例9.1】某系统由A、B二个模块构成,A模块要调用到B模块的一个过程disp,以显示一个字符。二个模块如下:模块A:EXTRN DISP:NEAR

.MODEL SMALL

.CODE

START:

CALL DISP

MOV AH,4CH

INT 21H

END START第9章模块化程序设计与混合编程第十一页,共八十页,2022年,8月28日模块B:PUBLIC DISP

.MODEL SMALL

.CODE

DISP PROC NEAR

MOV DL,'A'

MOV AH,2

INT 21H

RET

DISP ENDP

END第9章模块化程序设计与混合编程第十二页,共八十页,2022年,8月28日假设模块A存盘后文件名为A.ASM,模块B存盘后文件名为B.ASM,则可用以下命令行分别汇编,然后链接合并。

MASMA;

MASMB;

LINKA+B;

最后得到可执行文件A.EXE。执行该文件,可在屏幕上显示字符A。

在这个例子中,两个模块存储模式相同,并且都是用.CODE伪指令指定代码段,则二个模块内代码段的段名、定位类型、组合类型、类别等都由汇编程序自动生成(分别为:TEXT、WORD、PUBLIC、‘CODE’),由于二个段是同名段,且都为PUBLIC组合类型,故链接程序最后将其合并为一个段,因而模块A对模块B内disp的调用为段内调用。第9章模块化程序设计与混合编程第十三页,共八十页,2022年,8月28日【例9.2】段间调用。对例9-1中的二个模块作如下修改:EXTRN DISP:FAR

.MODEL SMALL

.CODE

START: CALL DISP MOV AH,4CH INT 21H END START模块A:模块B:PUBLIC DISP

CODE SEGMENT

ASSUME CS:CODE

DISP PROC FAR

MOV DL,'A'

MOV AH,2

INT 21H

RET

DISP ENDP

END第9章模块化程序设计与混合编程第十四页,共八十页,2022年,8月28日在这个例子中,由于模块B中代码段的段名与模块A中的不再一致,故二个段是各自独立的代码段,调用成为段间调用,disp不论在模块A中的外部符号声明还是在模块B中的类型定义,都必须为FAR类型。第9章模块化程序设计与混合编程第十五页,共八十页,2022年,8月28日2.模块间数据的访问

两个模块之间若要相互交换数据,可用如下几种办法实现:

a.通过寄存器传递

b.通过堆栈传递

c.互访对方变量或数据区

a、b二种方法较为简单,因为不论在同一模块内的各个过程间,还是在不同模块内的各个过程间,都中在共用相同的CPU寄存器和堆栈段,所以这种况下调用在模块内还是在模块外情况并无差别。下面主要讨论模块间互访对方变量或数据区的方法。第9章模块化程序设计与混合编程第十六页,共八十页,2022年,8月28日3.模块间互访对方变量或数据区的方法

一个模块要访问另一个模块内的数据,关键是调整数据段寄存器使之存储被访问数据所在段的段地址。【例9.3】演示一个模块内访问另一模块内变量或数据区的方法。

某工程由如下二个模块组成。第9章模块化程序设计与混合编程第十七页,共八十页,2022年,8月28日模块A:EXTRN SET:FAR

PUBLIC BUF1

.MODEL SMALL

.DATA

BUF1 DB 3

.CODE

START:

MOV AX,@DATA

MOV DS,AX

CALL SET

MOV DL,BUF1

MOV AH,2

INT 21H

MOV AH,4CH

INT 21H

END START第9章模块化程序设计与混合编程第十八页,共八十页,2022年,8月28日模块B:EXTRN BUF1:BYTE

PUBLIC SET

CODE SEGMENT

ASSUMECS:CODE

SET PROC FAR

MOV BUF1,'8'

RET

SET ENDP

CODE ENDS

END在这个例子中,当控制从模块A转到模块B后,DS寄存器仍指向模块A的数据段,所以模块B中可用DS直接访问模块A中的数据,这种情况相当于在一个模块中有二个代码段一个数据段的情况,不论控制在哪个代码段内,都可直接访问数据段。第9章模块化程序设计与混合编程第十九页,共八十页,2022年,8月28日【例9.4】二个模块都有自己的数据段时从一个模块访问另一个模块数据区的方法。对例9.3进行修改如下:EXTRN SET:FAR

PUBLIC BUF1

.MODEL SMALL

.DATA

BUF1 DB 3

.CODE

START:

MOV AX,@DATA

MOV DS,AX

CALL SET

MOV DL,BUF1

MOV AH,2

INT 21H

MOV AH,4CH

INT 21H

END START模块A:第9章模块化程序设计与混合编程第二十页,共八十页,2022年,8月28日模块B:EXTRN BUF1:BYTE

PUBLIC SET

DATA SEGMENT

BUF2 DB '5'

DATA ENDS

CODE SEGMENT

ASSUMECS:CODE,DS:DATA

SET PROC FAR

PUSH DS

MOV AX,DATA

MOV DS,AX

MOV BL,BUF2

MOV AX,SEGBUF1

MOV DS,AX

MOV AL,BUF1

ADD AL,BL

MOV BUF1,AL

POP DS

RET

SET ENDP

CODE ENDS

END运行显示:8第9章模块化程序设计与混合编程第二十一页,共八十页,2022年,8月28日这个例子中模块A与模块B各有自已的数据段,当控制从模块A转移到模块B时,DS仍指向模块A的数据段,由于模块B要使用DS访问自已的数据段,所以它先将DS压栈保存,然后使DS指向自己的数据段,完成数据的读取后,又修改DS使之指向模块A的数据段,从而访问模块A的BUF1变量。最后,在返回模块A前通过出栈将DS恢复为进入时的初值(本例中由于在访问模块A的变量BUF1时已使DS指向了模块A的数据段,保存恢复DS的工作可以省略)。这个例子的关键是访问外部模块的变量前事先要调整DS使之保存外部变量所在段的段地址。由于访问内存并不一定非要用DS,若用其它段寄存器(如ES),则除了要事先调整该段寄存器使之存储被访问外部变量所在段的段值外,还要在使用变量时在前面加上段超越标记,如可将模块B修改如下:第9章模块化程序设计与混合编程第二十二页,共八十页,2022年,8月28日EXTRN BUF1:BYTE

PUBLIC SET

DATA SEGMENT

BUF2 DB '5'

DATA ENDS

CODE SEGMENT

ASSUMECS:CODE,DS:DATA

SET PROC FAR

PUSH DS

MOV AX,DATA

MOV DS,AX

MOV BL,BUF2

MOV AX,SEGBUF1

MOV ES,AX

MOV AL,ES:BUF1 ;使用段超越标记

ADD AL,BL

MOV ES:BUF1,AL

POP DS

RET

SET ENDP

CODE ENDS

END第9章模块化程序设计与混合编程第二十三页,共八十页,2022年,8月28日模块间信息传递的另一个方法是利用COMMON段,只要二个模块中都定义一个同名同类别的数据段,且组合类型都为COMMON,由于这二个段在链接时要重合为一个段,只要在这二个段内将变量安排在相同的位置上,那么它们对应的是相同的存储区。【例9.5】 利用COMMON段访问另一个模块的数据区。

将上例进行修改,如下:第9章模块化程序设计与混合编程第二十四页,共八十页,2022年,8月28日模块A:EXTRN SET:FAR

DATA SEGMENT COMMON

BUF1 DB 3

BUF2 DB '0'

DATA ENDS

CODE1SEGMENT

ASSUME CS:CODE1,DS:DATA

START:

MOV AX,DATA

MOV DS,AX

CALL SET

MOV DL,BUF2

MOV AH,2

INT 21H

MOV AH,4CH

INT 21H

CODE1 ENDS

END START第9章模块化程序设计与混合编程第二十五页,共八十页,2022年,8月28日模块B:PUBLIC SET

DATA SEGMENT

BUF1 DB ?

BUF2 DB ?

DATA ENDS

CODE2SEGMENT

ASSUME CS:CODE2,DS:DATA

SET PROC FAR

MOV AL,BUF1

ADD BUF2,AL

RET

SET ENDP

CODE2 ENDS

END返回本章首页返回本节首页第9章模块化程序设计与混合编程第二十六页,共八十页,2022年,8月28日9.2汇编语言与高级语言的接口9.2.1概述9.2.2嵌入式汇编9.2.3汇编语言与C语言的接口第9章模块化程序设计与混合编程第二十七页,共八十页,2022年,8月28日9.2.1概述对于一项软件工程而言,首要目标是保证达到软件所需质量,其次是尽可能降低开发成本。汇编语言与高级语言在语言表达能力、表达的方便程度、编程效率和运行效率方面各有特点,这就需要它们相互“取长补短”,即混合编程,以便取得最大的综合效益。高级语言与汇编语言的混合编程通常发生在下列情况下:

①需要访问机器的硬件特征,这些特征用高级语言表达比较困难。

②某些程序段需要频烦运行,单次运行速度的提高可显著提高整个系统运行效率。

③有现成的汇编语言程序段可用。第9章模块化程序设计与混合编程第二十八页,共八十页,2022年,8月28日高级语言种类较多,本章主要讨论与C语言的混合编程问题,流行的C编译器主要有TurboC和MSC,本书主要以TurboC为例。

C语言的目标是追求效率,因而它与汇编语言与较多的“血源关系”。C语言提供了三种调用汇编语言功能的方法:(1)嵌入汇编(2)寄存器伪变量与bdos()、intdos()、int86()等系列DOS、BIOS服务调用函数(3)C语言与汇编语言的混合编程。其中第二种方法因主要涉及C语言的编程问题,本书不作讨论,请读者参考相关C语言书籍。返回本章首页返回上一节返回本节首页第9章模块化程序设计与混合编程第二十九页,共八十页,2022年,8月28日9.2.2嵌入式汇编

在C程序中可以直接插入汇编语言语句,称为嵌入式汇编。1.嵌入式汇编语句的格式asm[<标号>]<汇编指令或伪指令><参数><;或新行>说明:asm是嵌入汇编语句的关键字<汇编指令或伪指令>可是任何有效的汇编指令或伪指令<标号>是可选的。<参数>是汇编指令或伪指令的操作数,它可引用C语言中的常量、变量和标号。<;或新行>都表示asm语句的结束,一行内可写多条嵌入式汇编语句,它们以“;”分隔。一行内如果只有一个asm语句,则后面不需要“;”,asm语句是C语言中唯一的依靠换行的语句。asm语句如需要注释,必须采用C格式的注释,即“/*注释内容*/”编译后,函数外的汇编语句放在数据段内,函数内的汇编语句放在代码段内。第9章模块化程序设计与混合编程第三十页,共八十页,2022年,8月28日2.嵌入式汇编的编译连接含有嵌入式汇编的程序不能在TurboC集成开发环境下进行编译,因为集成开发环境不支持这一功能。这样的程序必须使用TurboC的命令行编译器TCC.EXE进行编译,而TCC.EXE在编译是又要调用TASM.EXE,所以当前目录下必须保证有汇编程序TASM.EXE。TCC.EXE调用格式为:其中file[s]需要编译或连接的文件,可有一个或多个。[options]为各个参数选项,常用的有“-B”用于指示源程序中使用了嵌入汇编,“–S”能使TCC.EXE编译成汇编语言的源程序输出。若程序中使用了嵌入汇编,调用TCC时必须选择-B参数,或者是在程序中使用伪指令“#pragmainline”,此伪指令功能与“-B”参数等效。TCC[options]file[s]第9章模块化程序设计与混合编程第三十一页,共八十页,2022年,8月28日【例9.6】嵌入式汇编的编译连接

有如下程序:

asmBUFDB“thisisatest $”

main()

{

asm MOV AH,9

asm MOV DX,OFFSET BUF

asm INT 21H

}

在TC的集成开发环境下编辑完以上程序后,存盘(假设文件名为test.c),然后以如下命令行进行编译连接:

tcc–Btest.c.

(注意:tcc的参数是区分大小写的,不要将“-B”写成“-b”)

则可生成test.exe文件,在DOS下运行之,即可在屏幕上显示“thisisatest”。第9章模块化程序设计与混合编程第三十二页,共八十页,2022年,8月28日【例9.7】从嵌入汇编语句中访问C定义的局部及全局变量。分析如下程序:#include <stdio.h>

int i=3;

main()

{

char j='a';

struct s{

int x;

int y;

}stru;

stru.x=50;

stru.y=20;

printf("\nj=%c", j);

asm inc byte ptr j

printf("\nj=%c", j);

printf("\ni=%d", i);

asm mov ax,stru.x

asm sub ax,stru.y

asm mov i,ax

printf("\ni=%d", i);

}该程序的运行结果如下:j=aj=bi=3i=30第9章模块化程序设计与混合编程第三十三页,共八十页,2022年,8月28日3.嵌入汇编的限制嵌入汇编码中的转移指令只能引用C中定义的标号,而嵌入汇编码中的非转移指令则可引用除标号以外的任何C元素。

在嵌入汇编码的末尾,下列寄存器的值必须与进入嵌入汇编码时的值保持一致:BP、SP、CS、DS、SS。如果C程序中用到了寄存器变量,还需再保证SI、DI的值不变,其它寄存器的值可自由改变。返回本章首页返回本节首页第9章模块化程序设计与混合编程第三十四页,共八十页,2022年,8月28日9.2.3汇编语言与C语言的混合编程1.调用约定在混合编程中,双方在如下方面必须遵循相同的接口约定,才可使双方的代码协同工作。①内存模式:即对内存的使用,主要是段、组的划分,以及段名、组名的约定等。若二种语言对段的划分情况不同,则双方指针或地址的使用便不同,这时极易发生运行错误。如在函数调用中,一方将四节地址压栈,而另一方则只将二字节地址出栈,可能造成程序崩溃。②命名约定:即标识符的命名规则,以及是否区分大小写等。③调用约定:调用者以什么顺序将参数压栈,被调用者以什么顺序提取参数,由谁负责最后清除堆栈中的参数等。④参数传递约定:调用参数以什么方式传递的,例如传递的是参数的值还是参数的地址等。第9章模块化程序设计与混合编程第三十五页,共八十页,2022年,8月28日2.TucboC的六种存储模式

为允许程序员决定内存布局结构,以便更合理高效地使用内存,TucboC提供了六种存储模式(MSC相同),如下:①极小模式:程序的所有代码和数据限制在64K内,即程序只有一个段,CS=DS=ES=SS,所有指针全部为near指针。②小模式:代码和数据各占一个段,因而代码和数据分别不能超过64K。DS=SS=ES,所有指针都为near指针。③中模式:代码段用far指针,数据段用near指针,代码段可多达1M,数据不能超过64K。④紧凑模式:数据段用far指针,代码段用near指针。代码最多64K,数据可多达1M。⑤大模式:代码、数据均用far指针,因而均可多达1M。⑥极大模式:代码、数据均用far指针。TurboC一般限制静态数据为64K,极大模式下可打破此限制。第9章模块化程序设计与混合编程第三十六页,共八十页,2022年,8月28日3.TurboC的调用约定

为了解TurboC的调用接口机制,观察其以汇编形式给出的编译结果是最好的方式。假设有如下C程序:#include <stdio.h>

int para1=10 ;

int para2;

int add(int a,int b)

{

return (a+b);

}

main()

{

para1++ ;

para2=add(para1,20);

printf("\n%d\n%d",para1,para2);

}以上程序,设存盘后文件名为test.c,则可用如下命令行编译成汇编语言结果输出:TCC -ms -S test.c第9章模块化程序设计与混合编程第三十七页,共八十页,2022年,8月28日输出结果为(为便于理解,添加了中文注释):_TEXT segment byte public 'CODE‘ ;代码段DGROUP group _DATA,_BSS assume cs:_TEXT,ds:DGROUP,ss:DGROUP_TEXT ends_DATA segment word public 'DATA' ;数据段_para1 label word dw 10_DATA ends_TEXT segment byte public 'CODE' ;代码段; ?debug L5 ;注明以下代码是由源程序的第5行产生,下同第9章模块化程序设计与混合编程第三十八页,共八十页,2022年,8月28日_add proc near ;函数add

push bp

mov bp,sp

; ?debug L7

mov ax,word ptr [bp+4] ;取第一个参数

add ax,word ptr [bp+6] ;取第二个参数,并加

;到在AX内的第一个参数上

jmp short @1

@1:

; ?debug L8

pop bp

ret

_add endp

; ?debug L10第9章模块化程序设计与混合编程第三十九页,共八十页,2022年,8月28日_main proc near ;主函数

; ?debug L12

inc word ptr DGROUP:_para1 ;语句para1++

; ?debug L13

mov ax,20

push ax ;为调用add压栈第二个参数(最右参数)

push word ptr DGROUP:_para1

;为调用add压栈第一个参数

call near ptr _add ;调用add函数

pop cx

pop cx ;以上两句清除堆栈中的二个参数

mov word ptr DGROUP:_para2,ax ;add返回值在AX

;中,将其赋给para2

; ?debug L 14

push word ptr DGROUP:_para2第9章模块化程序设计与混合编程第四十页,共八十页,2022年,8月28日;为调用printf压栈最右参数

push word ptr DGROUP:_para1

;为调用printf压栈第二个参数

mov ax,offset DGROUP:s@

push ax ;为调用printf压栈第一个参数(最左参数)

call near ptr _printf;调用printf

add sp,6 ;清除堆栈中的三个参数

@2:

; ?debug L15

ret

_main endp

_TEXT ends第9章模块化程序设计与混合编程第四十一页,共八十页,2022年,8月28日_BSS segment word public 'BSS' ;未初始化的数据段

_para2 label word

db 2 dup (?)

_BSS ends

?debug C E9

_DATA segment word public 'DATA’ ;数据段

s@ label byte ;定义字符串“\n%d\n%d”

db 10

db 37

db 100

db 10

db 37

db 100

db 0

_DATA ends第9章模块化程序设计与混合编程第四十二页,共八十页,2022年,8月28日_TEXT segment byte public 'CODE' ;代码段

extrn _printf:near ;声明外部函数

_TEXT ends

public _main ;声明公共标识符

public _add

public _para2

public _para1

end第9章模块化程序设计与混合编程第四十三页,共八十页,2022年,8月28日说明:(1)汇编模块要遵循与TurboC一致的内存模式,同时采用TurboC的段名约定,这可用二种方法实现:①使用简化的段定义方式,只要用伪指令.MODEL说明内存模式为SMALL,然后用伪指令.CODE定义代码段,用伪指令.DATA定义数据段,则生成的段名与TurboC的段名一致。②先编写一个只有空架的C程序,用TurboC的-B参数编译生成汇编输出,即得到了完整的程序框架。(2)TurboC在编译时会自动在全局变量及函数名前加上下划线,如果汇编模块中的函数或变量要供TurboC访问,变量名或函数名也必须以下划线开头。另外C语言大小写敏感,而汇编语言大小写不敏感,为不致命名混淆,应使汇编语言也保持大小写敏感,汇编程序的命令行选项/ml(所有符号区分大小写)和/mx(仅公共标识符和外部标识符区分大小写)可做到这一点。第9章模块化程序设计与混合编程第四十四页,共八十页,2022年,8月28日堆栈顶a.被调函数入口处堆栈结构……参数2参数1返回地址……SP→堆栈顶b.被调函数准备提取参数时的堆栈结构……参数2参数1返回地址BP……SP→←BP←BP+2←BP+4←BP+6图9-2C函数调用中的堆栈结构(3)C语言的参数均通过堆栈传递,且自右向左压栈。当程序进入被调函数时,堆栈结果如图9-2a所示:C语言的被调函数是通过BP间址的方式访问堆栈的,为不破坏BP的值,在使用BP前将其压栈,这时各参数的位置如图9-2b所示。第9章模块化程序设计与混合编程第四十五页,共八十页,2022年,8月28日C语言中基本数据类型(char、shortint、int、longint、unsignedint、float、double)以及结构类型的参数都是以传值方式传递的,而数组类型的参数是传址方式传递的,而有些参数要先进行类型转换后再压栈传递(如char先转换为int)。基本情况如表9-1所示:注:若函数调用是远调用,则栈顶部的返回地址为4字节,各参数的偏移值应相应修改。第9章模块化程序设计与混合编程第四十六页,共八十页,2022年,8月28日表9-1C语言参数的传递方式类型字节数类型字节数char2unsignedlong4unsignedchar2float8short2double8int2near指针2(偏移量)unsignedint2far指针8(段:偏移量)long4第9章模块化程序设计与混合编程第四十七页,共八十页,2022年,8月28日

(4)被调函数的返回值通常放在AX(16位)或DX:AX(32位)内,但若是float、double、struct、union类型,则返回值放入静态数据区,然后返回其指针。(5)被调函数要保护的寄存器,同嵌入汇编中对寄存器的限制情况相同。第9章模块化程序设计与混合编程第四十八页,共八十页,2022年,8月28日【例9.8】编写一个能按用户指定的内存地址及长度以十六进制格式显示内存单元内容的程序。

分析:从键盘接收用户输入的内存地址及内存长度的工作可由C语言完成,因为C语言的库函数printf()及scanf()在给出输入提示,读取用户输入方面比较方便。对于读取指定地址处的数据,汇编语言则是最方便的,故这部分工作由一个汇编模块完成。整个程序如下:C语言模块:第9章模块化程序设计与混合编程第四十九页,共八十页,2022年,8月28日#include <stdio.h>

extern void move(char*,unsignedint,unsignedint,unsignedint);

main()

{

unsigned intsegment,offset,number;

char buf[20];

int i;

printf("\n Pleaseinputtheaddress(segment:offset):");

scanf("%x:%x",&segment,&offset);

printf("\n Plesaeinputthenumber(lessthan20):");

scanf("%u",&number);

move(buf,number,offset,segment);

printf("\n");

for(i=0;i<number;i++)

printf(" %2x ",buf[i]);

}

(本模块在编译时用small模式)第9章模块化程序设计与混合编程第五十页,共八十页,2022年,8月28日汇编语言模块:

.MODEL SMALL

.CODE

PUBLIC _move

_move PROC

PUSH BP

MOV BP,SP

PUSH SI

PUSH DI

PUSH DS

MOV DI,[BP+4]

MOV CX,[BP+6]

LDS SI,[BP+8]

CLD

REP MOVSB

POP DS

POP DI

POP SI

POP BP

_move ENDP

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

第9章模块化程序设计与混合编程第五十二页,共八十页,2022年,8月28日程序运行结果如下(画线部分为键入内容):Pleaseinputtheaddress(segment:offset):0x0000:0x002f

Plesaeinputthenumber(lessthan20):10

fff06fffef0fff06fffef0fff0ff9a第9章模块化程序设计与混合编程第五十三页,共八十页,2022年,8月28日从输出结果可以看出,有些内存单元的内容显示有误,因为是按字节显示的内存内容,一个数据应不大于FFH才对,而有些单元显示的却是四位十六进制数,错在哪儿呢?让我们分析一下C语言模块在编译后生成的部分汇编代码,其中语句

for(i=0 ;i<number ;i++)

printf("%2x",buf[i]) ;

对应的代码如下:第9章模块化程序设计与混合编程第五十四页,共八十页,2022年,8月28日 xor si,si ;完成 i=0

jmp short @5

@4: mov al,byte ptr [bp+si-20]

;取出buf[i]的内容,取一个字节,这是正确的

cbw ;将AL内容符号扩展为一个字(这是导致错误处)

push ax ;调用printf所需的第一个参数压栈

mov ax,offsetDGROUP:s@+83

push ax ;调用printf所需的第二个参数压栈

call near ptr_printf

pop cx

pop cx ;清除堆栈中的废参数

@3: inc si ;i++

@5: cmp si,word ptr[bp-22] ;i<number?

jb @4第9章模块化程序设计与混合编程第五十五页,共八十页,2022年,8月28日显然,原因在于因为压栈只能按字进行,AL内的待显示内容被CBW语句符号扩展为一个字,然后通过堆栈传递给了printf函数,而printf并不知道原参数是一个字还是一个字节,它按一个字进行了显示,若AL内的二进制数最高位1,则符号扩展后AH内为FFH,所以大于80H的数均在前面加显示了FFH。

在C语言条件下显然无法改正上述错误,我们将数据的显示工作移到汇编代码内进行,就很容易按我们的要求进行显示了。改正后的程序详见实训二。返回本章首页返回上一小节返回本节首页第9章模块化程序设计与混合编程第五十六页,共八十页,2022年,8月28日实训一键盘录入数据的转换与显示实训内容:编一程序,能从键盘读入二个五位十进制数(1位符号位+4位数值位),并将这二个十进制数分别转换为二进制数,然后求其和,再将和以十进制形式进行显示。

分析:这个程序主要练习十进制数与二进制数之间的相互转换。由于用户输入的数一般是用十进制表示的,而这些数在计算机内存储、运算时一般都用二进制,这就涉及到了十进制数向二进制数的转换问题;当计算结果需要显示或打印时,又要将二进制数转换为十进制数,这又涉及到二进制数如何转换为十进制数。由于在计算机内这种转换经常进行,所以二进制数与十进制数之间的转换算法是汇编语言程序员应掌握的基本算法之一。第9章模块化程序设计与混合编程第五十七页,共八十页,2022年,8月28日由于程序较大,我们将其分在二个模块内,模块A负责十制数的读取、二个数的求和,以及和的显示工作,模块B负责将十进制数转换为二进制,以及将二进制数转换为十进制数。由于二个模块内数据传递关系比较复杂,所以采用COMMON段可避免在模块间传递参数,使源程序结构清析。另外,为突出主要算法,减少不必要的细节考虑,将用户输入的数据限制在4位(不含符号位)以内,这样数值在-9999至+9999范围以内,转换为二进制后在16位补码的表示范围内,而其相加后的和也在16位补码的表示范围内;同时,在将二进制数转换为十进制数显示时,也只须考虑万位及以下数字的转换即可,不存在十万位以上的数字。

参考程序:第9章模块化程序设计与混合编程第五十八页,共八十页,2022年,8月28日模块A:

EXTRN ASC_TO_BIN:FAR,BIN_TO_ASCII:FAR

DATA SEGMENT COMMON

INPUT_MESSAGEDB 0AH,

DB‘PLEASEINPUTANUMBER(LESSTHAN5FIGURES):$’

IN_ASC_BUF DB 6 ;十进制数的输入缓冲区,共可接收6个字符

DB ? ;保留,用于10号调用时DOS填入实际输入字符个数

DB 6DUP(?);一个符号位,四位数字ASCII码,加上一个回车

;符,共计6字符

BIN_BUF1 DW ?;将第一个数转换为二进制后,放于此处

OUTPUT_MESSAGE DB 0AH,‘THESUM IS:’,‘$’

OUT_ASC_SUM DB 6DUP(?),‘$'

;将二个数的和转换为ASCII码后,放于此处,以供9号调用显示

DATA ENDS第9章模块化程序设计与混合编程第五十九页,共八十页,2022年,8月28日CODE SEGMENT

ASSUME CS:CODE,DS:DATA

START:

MOV AX,DATA

MOV DS,AX

MOV DX,OFFSET INPUT_MESSAGE

MOV AH,9

INT 21H ;提示输入一个数

MOV DX,OFFSET IN_ASC_BUF

MOV AH,10

INT 21H ;读取第一个数

CALL ASC_TO_BIN ;转换第一个数为二进制

MOV BIN_BUF1,AX ;保存第一个数

MOV DX,OFFSET INPUT_MESSAGE

MOV AH,9

INT 21H ;提示输入一个数第9章模块化程序设计与混合编程第六十页,共八十页,2022年,8月28日 MOV DX,OFFSET IN_ASC_BUF

MOV AH,10

INT 21H ;读取第二个数

CALL ASC_TO_BIN ;转换第二个数为二进制

ADD AX,BIN_BUF1 ;计算这二个数之和

CALL BIN_TO_ASCII

;将和转换为ASCII码,以供显示用

MOV DX,OFFSET OUTPUT_MESSAGE

MOV AH,9

INT 21H

MOV DX,OFFSET OUT_ASC_SUM

MOV AH,9

INT 21H

MOV AH,4CH

INT 21H

CODE ENDS

END START第9章模块化程序设计与混合编程第六十一页,共八十页,2022年,8月28日模块B:

PUBLIC ASC_TO_BIN,BIN_TO_ASCII

DATA SEGMENT COMMON

INPUT_MESSAGE DB0AH,

DB‘PLEASEINPUTANUMBER(LESSTHAN5FIGURES):$’

IN_ASC_BUF DB6 ;十进制数的输入缓冲区,共可接收6个字符

DB? ;保留,用于10号调用时DOS填入实际输入字符个数

DB6DUP(?)

;一个符号位,四位数字ASCII码,加上一个回车符,共计6字符

BIN_BUF1 DW? ;将第一个数转换为二进制后,放于此处

OUTPUT_MESSAGE DB0AH,'THESUMIS:','$'

OUT_ASC_SUM DB 6DUP(?),'$'

;将二个数的和转换为ASCII码后,放于此处,以供9号调用显示

DATA ENDS

CSEG SEGMENT

ASSUME CS:CSEG,DS:DATA

ASC_TO_BIN PROC FAR第9章模块化程序设计与混合编程第六十二页,共八十页,2022年,8月28日;ASCII码转换为二进制数

;入口:十进制数的ASCII码在IN_ASC_BUF内

;出口:转换后的二进制数在AX内

;算法:先将其转换成十进制数字,再用累加和乘10加X

;的方法变成二进制数,如将

;358转换为二进制数,可先将累加和赋0,再计算

;(((0*10+3)*10+5)*10+8),结果为二

;进制数,再由符号位决定是否需要求补。

MOV CL,IN_ASC_BUF+1 ;取字符个数

MOV CH,0

DEC CL ;扣除符号位

MOV BX,OFFSET IN_ASC_BUF+3

;调整BX指向十进制数的最高位

PUSH BX

PUSH CX第9章模块化程序设计与混合编程第六十三页,共八十页,2022年,8月28日L1:

MOV AL,[BX]

AND AL,0FH

MOV [BX],AL

INC BX

LOOP L1 ;将所有数字字符的高四位清0,使之变为数字值

POP CX

POP BX

MOV AX,0 ;累加和赋初值

MOV SI,10第9章模块化程序设计与混合编程第六十四页,共八十页,2022年,8月28日L2:

MUL SI

ADD AL,[BX]

ADC AH,0

INC BX

LOOP L2 ;累加乘10

CMP IN_ASC_BUF+2,'+'

JZ L3 ;符号是正号,转移

NEG AX ;符号是负号,求补

L3:

RET

ASC_TO_BIN ENDP第9章模块化程序设计与混合编程第六十五页,共八十页,2022年,8月28日BIN_TO_ASCII PROC FAR

;将二进制数转换为对应十进制数数字的ASCII码

;入口:二进制数在AX内

;出口:转换后的ASCII码在OUT_ASC_SUM变量内

;算法:AX中的数范围在+32767到-32768之间,先检查AX中

;的符号位,以决定输出“+”

;还是“-”,若是负数,应先求补,得到原码后即可与正数作

;统一处理。转换方法为将被转换

;的二进制数先除以10000,商;即为万位数,再将余数除以

;1000,商为千位数,以此类推,

;求出百、十位数,剩下的为个位数。最后,将各个数加上

;30H,即成为对应字符。

MOV OUT_ASC_SUM,'+'

CMP AX,0

JGE L4 ;不是负数,转移

NEG AX

MOV OUT_ASC_SUM,'-'第9章模块化程序设计与混合编程第六十六页,共八十页,2022年,8月28日L4:

CWD

MOV BX,10000

DIV BX

ADD AL,30H;将万位转换为数字(商应在AX内,但因为商不大于

;3,所以有效部分在AL内)

MOV OUT_ASC_SUM+1,AL;保存万位数字

MOV AX,DX ;将余数置入AX内,以便当作被除数

CWD

MOV BX,1000

DIV BX

ADD AL,30H

MOV OUT_ASC_SUM+2,AL;保存千位数字

MOV AX,DX ;将余数置入AX内,以便当作被除数

MOV BL,100

DIV BL

ADD AL,30H第9章模块化程序设计与混合编程第六十七页,共八十页,2022年,8月28日 MOV OUT_ASC_SUM+3,AL ;保存百位数字

MOV AL,AH

CBW

MOV BL,10

DIV BL

ADD AL,30H

MOV OUT_ASC_SUM+4,AL ;保存十位数字

ADD AH,30H

MOV OUT_ASC_SUM+5,AH ;保存个位数字

RET

BIN_TO_ASCII ENDP

CSEG ENDS

END第9章模块化程序设计与混合编程第六十八页,共八十页,2022年,8月28日程序运行结果如下(画线部分为键入内容):D:\MASM>A

PLEASEINPUTANUMBER(LESSTHAN5FIGUERS):-2000

PLEASEINPUTANUMBER(LESSTHAN5FIGUERS):+3310

THESUMIS:+01310返回本章首页第9章模块化程序设计与混合编程第六十九页,共八十页,2022年,8月28日实训二C语言调用汇编语言子程序进行数据传递与显示实训内容:同本章例9.8,但要克服例9.8中的显示缺陷

分析:因为在printf()中控制一个字节数据的显示格式比较困难,故我们将显示部分也在汇编模块中完成即可。

参考程序:

汇编程序:

.MODEL SMALL

.CODE

PUBLIC _disp ;声明全局符号,以便C模块调用CHAR_DISPLAYPROC

;将AL内“0000 XXXX”格式的数据以十六进制形式显示出来(一个字符)

;入口参数:在AL内(AL的高四位必须为0,低四位为待显示数据)

;出口参数:将对应十六进制字符显示在屏幕上第9章模块化程序设计与混合编程第七十页,共八十页,2022年,8月28日 CMP AL,9

JA @@1

;若AL>9,则需要显示A~F之间的一个字母,转到@@1处处理

MOV AH,2

MOV DL,AL

ADD DL,30H

INT 21H

RET

@@1:

SUB AL,0AH

MOV DL,'A'

ADD DL,AL

MOV AH,2

INT 21H

RET

CHAR_DISPLAY ENDP第9章模块化程序设计与混合编程第七十一页,共八十页,2022年,8月28日HEX_DISP PROC

;将AL内的数以十六进制格式显示在屏幕上

;入口参数:在AL内

;出口参数:对应数据显示在屏幕上(二个字符)

PUSH DX

PUSH CX

PUSH AX

MOV CL,4

SHR AL,CL

;将AL右移四位,使得高四位为0,原高四位数据到低四位上

CALL CHAR_DISPLAY;将AL内原高四位上的数据显示出来

POP AX ;恢复原AL内的数据

AND AL,0FH ;AL高四位清0,以使显示低四位上的数据

CALL CHAR_DISPLAY ;显示AL低四位上的数据

POP CX

POP DX

RET

HEX_DISP ENDP第9章模块化程序设计与混合编程第七十二页,共八十页,2022年,8月28日SPACE_DISP PROC

;显示二个空格,以便将屏幕上的十六进制数隔开,无入口参数

PUSH DX

PUSH AX

MOV DL,''

MOV AH,2

INT 21H

MOV DL,''

MOV AH,2

INT 21H

POP AX

POP DX

RET

SPACE_DISP ENDP第9章模块化程序设计与混合编程第七十三页,共八十页,202

温馨提示

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

评论

0/150

提交评论