第四章 模块化程序设计 (6学时 )
第二节 系统功能调用 (2学时 )
退 出
第一节 模块的设计 (2学时 )
?知 识 概 述 ?
第三节 在 C++中使用汇编语言 (2学时 )
第一节 模块的设计
4.1.1 模块化程序设计的原则
退 出
汇编语言只提供了模块化编程的条件,具体的模块划分、
模块设计及模块间的关系要有用户自己处理。下面仅提供一
些应考虑的因素。
模块的划分一般不要太大,也不宜过小,主要根据其功
能而定。每个模块的功能要明确、单一。
模块的独立性要强。即模块的功能由该模块自身完成,
不依赖其它模块。
4.1.1
每个模块最好只有 1个入口,1个出口。
模块间的关系要明确。即上层模块可调用下
层模块,下层模块可返回上层模块;反之不可以。
程序中已变化的部分与不易变化的部分分开,
形成不同的模块。
退 出
4.1.2 进程模块的设计和调用
一、多模块之间段的连接
多模块之间的连接,需用到 SEGMENT语句提供的
连接信息。段定义的完整形式为:
段名 SEGMENT [定位类型 ][组合类型 ][?类别 ?]
?
段名 ENDS
1,定位类型
对齐类型表示当前段对起始地址的要求,连接程序
( LINK.EXE)按 表 4.1的地址格式来定位段的起始地址。
退 出
4.1.2
组合类型是告诉连接程序如何把本段与其它段连接的有
关信息 。 具体的组合类型如 表 4.2所示,
3,?类别 ?
类别可以使任何一个合法的标识符,但必须用但引号
括起来。连接时,将把不同模块中同类别的各段在物理上
相邻地连接在一起。
退 出
4.1.2
二、模块间的交叉访问
模块间的交叉访问:是指一个模块要引用另一个模块
的变量、标号等标识符。
标识符就可能有两类:
一类是供本模块使用的,我们称之为局部标识符 ;
另一类是 同时可供本模块和其它模块使用的,我们称
之为全局标识符。
1,PUBLIC伪指令
伪指令 PUBLIC是用来说明,当前模块中哪些标识符
是能被其它模块引用的全局标识符。其说明的一般形式如
下:
PUBLIC 标识符 1,标识符 2,…
其中:“标识符”可以是变量名、过程名和程序标号,
各标识符之间要用逗号分开。
退 出
4.1.2
2,伪指令 EXTRN
伪指令 EXTRN是用来说明在当前模块所使用的标识
符中,哪些标识符是已在其它模块中被定义为指定类型的
标识符。
伪指令 EXTRN的一般说明形式如下:
EXTRN 标识符 1:类型 1,标识符 2:类型 2,…
其中:“标识符”和“类型”之间要用冒号“:”连
接。
近模块设计及调用:数据段连接起来后在同一逻辑段
内,代码也在同一逻辑代码段内,从而保证了变量、标识
符无论哪个模块访问是都具有相同的段基址,这就是一种
近程模块调用。例 4-1就是近程模块设计及调用的一个实
例。
退 出
4.1.2
例 4-1 将第三章例 3-22的浮点数结果显示。
分析,因为浮点数的显示按期模块化程序设计要求,
我们可把其编写成子程序模块,需要显示浮点数结果时,
随时调用可以完成其显示功能。若要将例 3-22的浮点数结
果显示,也许对例 3-22程序稍作修改,已完成调用显示模
块的功能。
主程序:
.MODEL SMALL
.386
.387
.STACK 200H
.DATA
EXTRN dada:DWORD
l1 REAL4 0.000001
退 出
4.1.2
c1 REAL4 0.000001
two REAL4 2.0
.CODE
.STARTUP
EXTRN disp:NEAR
FLD l1
FMUL c1
FSQRT
FMUL two
FLDPI
FMUL
FLD1 退 出
4.1.2
FDIVR
FSTP dada
CALL disp
.EXIT 0
END; disp子模块功能:把内存单元 dada(单精度)中的浮点数
显示;入口参数:变量 dada;出口参数:无
退 出
4.1.2;算法描述:; 1)我们可先判断 data的值的正负,若为负,则显示,-”。
取其整数部分给变量 whole,;取其小数部分给变量
fract。; 2)取其整数部分,采用除 10取余数的方法,求出整数数
码(先低位后高位),加 30H ;压入堆栈,然后通过出
栈显示,以达到先显示高位的目的。; 3)取其小数部分,把阶码变为原码,采用乘 10取整法,
求出小数部分(先高后低),;加 30H变为 ASCII码显
示。
退 出
4.1.2
disp子模块源程序:
.MODEL SMALL
.386
.387
.STACK 128
.DATA
PUBLIC dada
dada DD?
status DW?
whole DD?
fract DD?
.CODE
退 出
4.1.2
disps PROC
MOV AH,02
MOV DL,AL
INT 21H
RET
disps ENDP
PUBLIC disp
disp,FNINIT
MOV AX,@data
MOV DS,AX
FSTCW status
退 出
4.1.2
OR status,0C00H
FLDCW status
FLD dada
FTST
FSTSW AX
AND AX,4500h
CMP AX,0100h
JNZ positive
MOV al,'-'
CALL disps
FABS
退 出
4.1.2
positive:
FLD ST
FRNDINT
FIST whole
FSUBR
FABS
FSTP fract
MOV EAX,whole
MOV EBX,10
MOV CX,0
PUSH BX
退 出
4.1.2
again1:MOV EDX,0
DIV EBX
ADD DL,30H
PUSH DX
INC CX
CMP EAX,0
JNZ again1
disp1,POP AX
CALL disps
LOOP disp1
MOV AL,'.'
退 出
4.1.2
CALL disps
MOV EAX,fract
FSTCW status
XOR status,0C00h
FLDCW status
FLD fract
FXTRACT
FSTP fract
FABS
FISTP whole
MOV ECX,whole
MOV EAX,fract
退 出
4.1.2
SHL EAX,9
RCR EAX,CL
again2:MUL EBX
PUSH EAX
XCHG EAX,EDX
ADD AL,30H
CALL disps
POP EAX
CMP EAX,0
JNZ again2
RET
END
退 出
4.1.2
这就是一个近程模块的设计及调用的实例,要明白这
个问题,那么就要知道简化的段定义,在,model small模式
下段的缺省属性,其规定如 表 4.3所示。
退 出
4.1.3 远程模块的设计及调用
远程模块的设计及调用实际上就是主模块与子模块的
代码段不在同一逻辑段,数据段也可以在同一逻辑段,也
可不在同一逻辑段。用户只需把握两个原则:
一是若代码段不在同一段,子模块就要设计远程的,
若数据段也不在同一逻辑段,在内存变量的交叉访问时,
就要用不同的段寄存器来指向不同的数据段。
二是在标号与变量在用 public,extrn声明时,该语
句最好放在源程序的最前面,以免引起交叉访问的时,子
模块与变量无法正确的调用与引用。下面我们通过举例说
明。
退 出
4.1.3
例 4-2 显示一字符串 mess。
分析,这个程序的功能很容易实现,按理讲只用 dos的 9号
功能调用就可实现。但是为了说明远程模块的设计与调用
方法,我们有意把显示功能的实现,用显示单个字符的子
模块实现,若要显示字符串,通过循环调用子模块实现。
LI4-2ZHU.ASM主模块源程序:
EXTRN bChar:BYTE
EXTRN out_routine:FAR
stack SEGMENT para stack 'stack'
DW 256 DUP(?)
stack ENDS
退 出
4.1.3
code SEGMENT 'code'
ASSUME CS:code,DS:code
messe DB 'this is a routine',13,10
main,CLD
MOV AX,CS
MOV DS,AX
MOV SI,OFFSET messe
退 出
4.1.3
loop1,LODSB
MOV es:bChar,AL
CALL out_routine
CMP es:bChar,10
JNE loop1
MOV AH,4CH
INT 21H
code ENDS
END main
退 出
4.1.3; out_routine 子模块功能:把内存单元 bChar中的字符显示;入口参数:变量 bChar;出口参数:无;算法描述:我们通过 dos的 2号功能调用来实现单个字符的
显示。
out_routine子模块源程序:
PUBLIC bChar
PUBLIC out_routine
code SEGMENT 'code'
ASSUME CS:code,ES:code
bChar DB?
退 出
4.1.3
out_routine PROC FAR
MOV AX,CS
MOV ES,AX
MOV DL,ES:bChar
MOV AH,2
INT 21H
RETF
out_routine ENDP
code ENDS
END
退 出
4.1.4 子程序库
库文件对学过 C/C++语言程序设计的读者来说应该是
不会陌生的,该语言的程序设计环境提供了大量的库文件,
也就是说,提供了大量的标准函数,用户在调用某一库中
的函数时,只需用,#include”声明。在本节里,介绍读
者如何创建自己的库文件。
退 出
4.1.4
一、建立库文件
假设现有目标文件 sub1.obj,sub2.obj和 sub3.obj,要
用它们建立库文件 mylib.lib。可用下列方法来建立该库文
件:
方法 1:所有目标文件都准备好了,可一次性把它们加入
到库文件中。
…>lib mylib +sub1 +sub2 +sub3
方法 2:随着目标文件的逐个生成,而依次把它们加入到
库文件中。
…>lib mylib +sub1
…>lib mylib +sub2
…>lib mylib +sub3
退 出
4.1.4
二、库文件应用
例如,我们用建立库文件的方法,完成例 4-2。例 4-2主程
序及子程序的建立及宏汇编过程同上,现已生成了目标文
件 LI4-2ZHU.obj及 LI42ZI.obj(因为库命令输入的目标文件
名不允许用,-”,所以取消了该字符 ),用 LI4-2ZI.obj建立
库文件 mylib.lib。用下列命令:
…>lib mylib LI42ZI
连接目标文件 LI4-2ZHU及库文件 mylib.lib:
…>link LI4 -2ZHU
执行 LI4-2程序:
…>LI4 -2
退 出
4.1.4
三、库文件与子程序不同
方法 1:直接连接目标文件而生成可执行文件。
这种方法简单、方便,也是常用的一种方法,但在连
接时,LINK程序会把目标文件中的所有代码都嵌入到执行
文件中,从而使得:包含在某目标文件中、但并没有被调
用的子程序代码也出现在执行文件中。这种情况无疑增加
了执行文件的字节数。
方法 2:采用子程序库的方法。
库文件可以把它看成是子程序的集合。库文件中存储
着子程序名、子程序的目标代码以及连接所需要的重定位
信息。当某目标文件与库文件相连接时,LINK程序只把目
标文件所用到的子程序从库文件中找出来,并合并到最终
的可执行文件中,而不是把库中所含的全部子程序都纳入
最后的可执行文件。
退 出
第二节 系统功能调用
BIOS层模块,DOS层功能模块:
BIOS层模块,DOS层功能模块对用
户来说均可看成中断处理程序,它们
的入口都在中断向量表中。用户使用
汇编语言可以直接调用它们,这极大
的方便了用户对微机系统的开发。
Windows层功能调用,Windows 系统
功能调用是通过 Win32 API函数调用
来实现的。
退 出
系统功能调用
4.2.1 BIOS层功能模块的调用
基本输入输出系统是操作系统核心。它的主要功能是
驱动系统的外部设备,如磁盘驱动器、显示器、键盘、打
印机及异步通信接口等。用户不必过多地关心有关设备的
物理性能及接口方面的细节,即不用直接使用 IN或 OUT语
句,只需调用相应的子程序即可实现设备的使用。其调用
子程序时需完成下列 3步:
第一,置入口参数;第二,选功能号于 AH中;
第三,使用,INT 中断号”语句调用。
基本输入输出系统中子程序的中断号为 5— 1FH。使用
,INT 中断号”语句即可调用相应的子程序,然后就可以
得到结果和输出参数。下面我们介绍最常用的 BIOS调用。
退 出
4.2.1
一,BIOS中的键盘输入
在 BIOS系统中,提供了中断 16H来实现键盘输入功能,
其具体的功能如下:
00H—— 从键盘读一个字符,输入字符不回显。
01H—— 判断键盘缓冲区内是否有字符可读。
02H—— 读取当前键盘状态字。
有关中断功能的详细描述和调用参数在此从略,需要
查阅者可参阅 附录 3。例如从键盘读入一个字符,需完成
以下 3步:
第一,入口参数:无;
第二,AH?00H;
第三,INT 16H;
其出口参数,也就是键入的字符的 ASCII码在 AL中。
退 出
4.2.1
二,BIOS中的屏幕输出
BIOS系统提供了中断 10H来实现各种屏幕处理功能。其具
体的功能如下:
00H— 设置显示器模式 01H— 设置光标形状
02H— 设置光标位置 03H— 读取光标信息
05H— 设置显示页 06H、( 07H) — 向上滚屏和(向下滚屏)
08H— 读光标处的字符及其属性 09H— 在当前光标处按指定属性显示字符
0AH— 在当前光标处显示字符 0CH— 写图形象素
0DH— 读图形象素 0FH— 读取显示器模式
退 出
4.2.1
例如在显示器上显示一个字符需完成以下 3步:
第一,入口参数,AL?需显示字符的 ASCII码,
BH ?页号,BL ?字符的显示属性。
第二,AH?09H;
第三,INT 10H
执行完成后,就会在显示器上按指定的字符属
性显示该字符。
退 出
4.2.2 DOS层功能调用
DOS层的功能模块在系统盘中,系统启动时被装
入内存。它们的功能比 BIOS更加广泛完整,主要功能
是文件管理、存储管理,设备管理等。
在 DOS下,其调用子程序时需完成下列 3步:
第一,置入口信息;
第二,选功能号于 AH中;
第三,通常使用,INT 21H”语句调用。
退 出
4.2.2
一,DOS中的键盘输入
键盘输入是一种最常用的输入方式,所以,在 DOS操
作系统中,提供了能实现各种键盘输入的功能,INT 21H
中的相关功能如下:
01H—— 带回显的键盘输入 06H—— 控制台的输入 /输出:当
DL=0FFH,表示键盘输入
08H—— 不回显的键盘输入 0AH—— 键盘输入字符串
0BH—— 检查键盘有无输入 0CH—— 清除输入缓冲区的输入
功能
退 出
4.2.2
例如带回显的键入单个字符的功能调用,需完成以下 3步:
第一,选功能号于 AH中;
第二,入口参数:无;
第三,通常使用,INT 21H”语句调用。
执行完成后,用户可从 AL取出输入字符的 ASCII码。
又如键入字符串功能调用,需完成以下 3步:
第一,功能号,AH?0AH;
第二,入口参数,DS:DX?存储键入字符串的首地址,
( DS:DX) =允许键入字符的个数;
第三,中断指令,INT 21H。
执行完成后,用户可从 DS:(DX+2)单元取出输入字符的
ASCII码,从 DS:(DX+1)取出实际键入字符的个数。
退 出
4.2.2
二,DOS中的显示器输出
屏幕输出是最常用的一种输出形式,DOS操作系统
提供了几种实现屏幕输出的功能调用。 INT 21H中的相关
功能如下:
02H— 显示的字符
06H— 控制台的输入 /输出:当 DL≠0FFH,表示显示字符
09H— 在屏幕上显示一个字符串
例如显示单个字符,需完成以下 3步:
第一,功能号,AH?02H;
第二,入口参数,DL?要显示字符的 ASCII码;
第三,INT 21H
执行完成后,屏幕上就会显示出该字符。
退 出
4.2.2
又如显示一个字符串,需完成以下 3步:
第一,功能号,AH?09H;
第二,入口参数,DS:DX?字符串首地址,字符串是以
,$”结束;
第三,INT 21H
执行完成后,屏幕上就会显示出该字符串 。
例 4-3 用键盘最多输入 10个字符,并存入内存变量 Buff中,
若按,Enter”键,则表示输入结束。
分析,实现该功能,我们可直接调用系统功能模块,可用
DOS层,也可用 BIOS层,但可以看出,使用 DOS层的程
序简单,容易。下面我们给出了分别用 BIOS层和 DOS层
的功能调用实现的程序。
退 出
4.2.2
1,用 BIOS层功能调用实现的源程序:
.MODEL SMALL
cr EQU 0DH
.STACK 200H
.DATA
Buff DB 10 DUP(?)
.CODE
.STARTUP
MOV CX,0AH
LEA BX,Buff
.REPEAT
MOV AH,0H
INT 16H
退 出
4.2.2
.BREAK,IF AL==cr
MOV [BX],AL
INC BX
.UNTILCXZ
.EXIT 0
END
2,用 DOS层功能调用实现的源程序:
.MODEL SMALL
.STACK 200H
.DATA
退 出
4.2.2
Buff DB 10,?,10 DUP(?)
.CODE
.STARTUP
LEA DX,Buff
MOV AH,0AH
INT 21H
.EXIT 0
END
可以看出,用 DOS层的功能调用程序要简单的多。
退 出
4.2.3 WINDOWS层功能模块调用
Win32程序是构筑在 Win32 API基础上的。
通过 Win32 API调用 Windows 系统相当于在 MS-DOS
中通过中断方式调用系统功能。 Win32 的系统功能模块放
在 Windows 的动态链接库中,DLL 是一种 Windows 的可
执行文件。
Win32 API 的核心由 3 个 DLL 提供,它们是:
退 出
4.2.3
KERNEL32.DLL— 系统服务功能。包括内存管理、任
务管理和动态链接等。
GDI32.DLL— 图形设备接口。利用 VGA 与 DRV 之类
的显示设备驱动程序完成显示文本和矩形等功能。
USER32.DLL— 用户接口服务。建立窗口和传送消息
等。
当然,Win32 API 还包括其它很多函数,这些也是由
DLL 提供的,不同的 DLL 提供了不同的系统功能。
退 出
4.2.3
一,API函数调用
C语言的消息框函数的声明:
int MessageBox(
HWND hWnd,// handle to owner window
LPCTSTR lpText,// text in message box
LPCTSTR lpCaption,// message box title
UINT uType // message box style
);
最后还有一句说明:
Library,Use User32.lib.
退 出
4.2.3
汇编的声明格式:
MessageBox Proto
hWnd:dword,lpText:dword,lpCaption:dword,uType:dword
上面最后一句 Library,Use User32.lib 则说明了这个
函数包括在 User32.dll 中。
汇编中调用 MessageBox 函数的方法是:
push uType
push lpCaption
push lpText
push hWnd
call MessageBox
退 出
4.2.3
1,使用 invoke 语句
Microsoft 在 MASM6.11中提供了一条伪指令实现利用
堆栈传递参数的子程序调用,那就是 invoke 伪指令,它的
格式是:
invoke 函数名 [,参数 1][,参数 2]..
对 MessageBox 的调用在 MASM 中可以写成:
invoke MessageBox,NULL,offset szText,offset
szCaption,MB_OK
退 出
4.2.3
2,API 函数的返回值
有的 API 函数有返回值,如 MessageBox 定义的返回
值是 int类型的数,返回值的类型对汇编程序来说也只有
dword 一种类型,它永远放在 eax 中。如果要返回的内容
不是一个 eax所能容纳的,Win32 API 采用的方法一般是
返回一个指针,或者在调用参数中提供一个缓冲区地址,
干脆把数据直接返回到缓冲区中去。
3,函数的声明
在调用 API 函数的时候,函数原型也必须预先声明,
否则,编译器会不认这个函数。 invoke伪指令也无法检查
参数个数。声明函数的格式是:
函数名 proto [距离 ] [语言 ] [参数 1]:数据类型,[参数
2]:数据类型,
退 出
4.2.3
4,include 语句
现在回到 Win32 Hello World 程序,这个程序用到了
两个 API 函数,MessageBox 和 ExitProcess,其函数声明
分别在 User32.dll 和 Kernel32.dll 中,在 MASM32 工具包
中已经包括了所有 DLL 的 API 函数声明列表,每个 DLL对
应 <DLL 名,inc>文件,在源程序中只要使用 include 语句
包含进来就可以了:
include user32.inc
include kernel32.inc
include 语句的语法是:
include 文件名
或 include <文件名 >
当遇到要包括的文件名和 MASM的关键字同名等可能
会引起编译器混淆的情况时,可以用 <>将文件名括起来。
退 出
4.2.3
5,includelib语句
为了告诉链接程序使用哪个导入库,使用的语句是:
includelib 库文件名
或 includelib <库文件名 >
和 include 的用法一样,在要包括让编译器混淆的文
件名时,可以用 <>将文件名括起来。例 3-23程序用到的两
个 API 函数 MessageBox 和 ExitProcess 分别在 User32.dll
和 Kernel32.dll 中,那么在源程序使用的相应语句为:
includelib user32.lib
includelib kernel32.lib
和 include 语句的处理不同,includelib 不会把,lib 文
件插入到源程序中,它只是告诉链接器在链接的时候到指
定的库文件中去找而已。
退 出
4.2.3
二、参数中的等值定义
再回过头来看显示消息框的语句:
invoke MessageBox,NULL,offset szText,offset
szCaption,MB_OK
uType — 定义对话框的类型,这个参数可以是以下参数:
要定义消息框上显示按钮,用下面的某一个标志:
MB_ABORTRETRYIGNORE — 消息框有三个按钮:
“终止”,“重试”和“忽略”。
MB_HELP — 消息框上显示一个“帮助”按钮,按下
后发送 WM_HELP 消息。
MB_OK — 消息框上显示一个“确定”按钮,这是默
认值。
退 出
4.2.3
MB_OKCANCEL — 消息框上显示两个按钮:“确定”
和“取消”。
MB_RETRYCANCEL — 消息框上显示两个按钮:
“重试”和“忽略”。
MB_YESNO — 消息框上显示两个按钮:“是”和
“否”
MB_YESNOCANCEL — 消息框上显示三个按钮:
“是”、“否”和“取消”。
要在消息框中显示图标,用下面的某一个标志:
MB_ICONWARNING — 显示惊叹号图标。
MB_ICONINFORMATION — 显示消息图标。
MB_ICONASTERISK — 显示危险图标。
MB_ICONQUESTION — 显示问号图标。
MB_ICONSTOP — 显示停止图标。
退 出
第三节 在 C++中使用汇编语言
16位的应用程序用 C/C++实现时,通常使用 Microsoft
Visual C++ for DOS平台,而 32位的应用程序用 C++实现
时,通常使用 Microsoft Visual C++ for Windows平台。在
用户使用汇编语言与 C++实现混合编程时 16位应用程序于
32位应用程序的主要区别,就是 Microsoft Visual C++ for
Windows应用程序试图用 DOS INT 21H功能,程序可能会
崩溃,因为其不允许直接调用 DOS。下面,我们主要介绍
32位应用程序中汇编语言与 C++的混合编程思想,从而对
于 16位的混合编程方法也就随之了解。
退 出
4.3.1 在 16位应用程序中使用
汇编语言与 C++语言
4.3.2 在 32位应用程序中使用汇编语
言与 C++语言
汇编与 C++混合编程的一种较为简单的方法是在 C++
程序中内嵌汇编语句,嵌入汇编语言的语法如下:
_asm <汇编指令 > <; 汇编指令 > ?
注意:这里的分号 ';' 不是汇编语言中起注释作用的分
号,而是作为语句的分隔符。
若 C语言源程序中嵌入一条汇编语句,则可按下列方
式来做:
_asm mov ax,data
若要嵌入一组汇编语句,则需要用括号 '{' 和 '}' 把它
们括起来。
退 出
4.3.2
asm {
mov ax,data1
xchg ax,data2
mov data1,ax //实现整型变量 data1和
data2之值的交换
}
例 4-4 以二进制到十六进制之间的任意进制显示键入的数
值( 0?32767)。
分析,首先,把输入的 ASCII数码 ?二进制数;
其次,通过循环调用 disps函数将该数转换为二进
制到十六进制之间的任意进制 的 ASCII数
码,再显示。
退 出
4.3.2
源程序:
#include <conio.h>
char *buffer="Enter a nember between 0 and
32767:";
char *buffer1="Base";
int a,b=0;
//disps子函数功能:将二进制数 data转换为 base进
制数显示;
//入口参数,int型的基数 base,int 型的 data;
//出口参数:无;
//算法描述:除基数取余数法,直至商为 0。
void disps(int base,int data){
int temp;
_asm{
mov eax,data
mov ebx,base
push ebx
退 出
4.3.2
Top1,mov edx,0
div ebx
push edx
cmp eax,0
jnz top1
top2,pop edx
cmp edx,ebx
je top4
add edx,30h
cmp edx,39h
jbe top3
add edx,7
退 出
4.3.2
top3,mov temp,edx
}
_putch(temp);
_asm {jmp top2}
top4:;
}
void main(){
int i;
_cputs(buffer);
a=_getche();
while(a>='0'&&a<='9'){
_asm{sub a,30h}
b=b*10+a;
a=_getche();
} 退 出
4.3.2
_putch(10);
_putch(13);
for(i=2;i<17;i++){
_cputs(buffer1);
disps(10,i);
_putch(' ');
disps(i,b);
_putch(10);
_putch(13);
}
}
退 出
4.3.3 在 C/C++应用程序中调用汇编语
言程序
下面我们通过例 4-5来说明 C++如何调用独立的汇编子模块。
例 4-5 将一字符串反序输出显示。
分析,我们利用 C++来实现字符串的显示功能,用汇编模块
实现反序功能。; Reverse子模块功能:将 arraychar指向的字符串反序;入口参数,char型的指针 arraychar;出口参数:无;算法:逐一取出字符入栈,利用堆栈的“后进先出”存储
原则,然后逐一出栈,再从 ;字符的首地址逐一存入。
退 出
4.3.3
.386
.model flat,c
.stack 1024
.code
public Reverse
Reverse proc uses esi,arraychar:ptr
mov esi,arraychar
push 0
.repeat
mov al,[esi]
push ax
inc esi
退 出
4.3.3
.until byte ptr[esi]==0
mov esi,arraychar
.while 1
pop ax
.break,if al==0
mov [esi],al
inc esi
.endw
ret
Reverse endp
end
退 出
4.3.3
主模块源程序:
#include <iostream.h>
extern "C"void Reverse(char*);
char chararray[30]="This is the mix programme!";
void main(){
cout<<chararray<<'\n';
Reverse(chararray);
cout<<chararray<<'\n';
}
退 出
表 4.1 段对齐类型与段起始地址之间的对应
关系
退 出
表 4.1段对齐类型与段起始地址之间的对应关系
对齐
类型
起始地址 (二进制 ) 功能说明 最多的空闲字节数
BYT
E
xxxx xxxx xxxx
xxxx xxxx
下一个字
节地址
0
WO
RD
xxxx xxxx xxxx
xxxx xxx0
下一个字
地址
1
DW
ORD
xxxx xxxx xxxx
xxxx xx00
下一个双
字地址
3
PAR
A
xxxx xxxx xxxx
xxxx 0000
下一个节
地址
15
PAG
E
xxxx xxxx xxxx
0000 0000
下一个页
地址
127
表 4.2 段的组合类型与段的连接之间的关系
退 出表 4.2 段的组合类型与段的连接之间的关系
组合类型
表示意义
NONE 表示当前段在逻辑上独立于其它模块,并有其自己的基地
址。 NONE是缺省的组合类型。
PUBLIC 表示当前段与其它模块中同段名的 PUBLIC类型段组合成一
个段。组合的先后次序取决于 LINK程序中目标模块排列的
次序。在组合时,后续段的起始地址要按其对齐类型进行
定位,所以,同名段之间可能有间隔。
COMMO
N
表示当前段与其它模块中同名段重叠,也就是说,它们的
起始地址相同。最终段的长度是同名段的最大长度。由于
段覆盖,所以,前一同名段中的初始化数据被后续段的初
始数据覆盖掉。
STACK 组合类型 STACK表示当前段是堆栈栈,其组合情况与
PUBLIC相同。
AT 数值
表达式
该数值表达式是当前段所指定的绝对起始地址的段地址。
表 4.3 小模式下简化段定义的缺省属性表
退 出
表 4.3 小模式下简化段定义的缺省属性表
伪指令 缺省段名 对齐类型 组合类型 类别
.CODE _TEXT WORD PUBLIC 'CODE'
.FARDAT
A
FAR_DAT
A
PARA NONE 'FAR_DAT
A'
.FARDAT
A?
FAR_BSS PARA NONE 'FAR_BSS
'
.STACK STACK PARA STACK 'STACK'
.DATA DATA WORD PUBLIC 'DATA'
.DATA? BSS WORD PUBLIC 'BSS'
.CONST CONST WORD PUBLIC 'CONST'
知 识 概 述
? 基本概念:模块化的设计原则,全局标识符,局
部标识符,子程序库。
? 重点:近、远模块调用。
? 难点,C++调用汇编子程序。
退 出