📄 汇006.txt
字号:
了解的内容:段各类属性(如:对齐类型、组合类型等)的含义,源程序的各种辅助说明伪指令。
掌握的内容:段寄存器说明语句的作用,堆栈段定义的特殊性。
熟练掌握的内容:段的完整定义和简化定义。
程序的三大结构(顺序结构、分支结构和循环结构等)在汇编语言中的表现形式,高级语言的程序结构向汇编语言的程序结构转换的一般方法。
汇编语言的常用编程工具——MASM或Tubro Debug——的使用,能在该编程环境下完成程序的编辑、汇编、调试和运行等步骤。
建议学习时间:10小时。第6章 程序的基本结构
在前面几章,我们分别介绍了用汇编语言进行程序设计所需要的几个最基本的知识:内存单元的寻址方式,变量定义和各种汇编指令格式。在掌握了这些基本内容之后,就需要学习如何把它们组成一个完整的汇编语言程序。
6.1 源程序的基本组成
汇编语言源程序的组成部分有:模块、段、子程序和宏等。一个模块对应一个目标文件,当开发较大型的应用程序时,该程序可能由若干个目标文件或库结合而成的。有关模块和子程序的知识和宏在第7章介绍,有关宏的知识将在第9章中叙述。
6.1.1 段的定义
微机系统的内存是分段管理的,为了与之相对应,汇编语言源程序也分若干个段来构成。8086CPU有四个段寄存器,在该系统环境下运行的程序在某个时刻最多可访问四个段,而80386及其以后的CPU都含有六个段寄存器,于是,在这些系统环境下开发的运行程序在某个时刻最多可访问六个段。
不论程序在某个时刻最多能访问多少个段,在编程序时,程序员都可以定义比该段数更多的段。在通常情况下,一个段的长度不能超过64K,在80386及其以后系统的保护方式下,段基地址是32位,段的最大长度可达4G。
段的长度是指该段所占的字节数:
、如果段是数据段,则其长度是其所有变量所占字节数的总和;
、如果段是代码段,则其长度是其所有指令所占字节数的总和。
在定义段时,每个段都有一个段名。在取段名时,要取一个具有一定含义的段名。
段定义的一般格式如下:
段名 SEGMENT [对齐类型] [组合类型] [类别]
… ;段内的具体内容
…
段名 ENDS
其中:“段名”必须是一个合法的标识符,前后二个段名要相同。可选项“对齐类型”、“组合类型”和“类别”的说明作用请见6.3节中的叙述。
一个数据段的定义例子:
DATA1 SEGMENT
word1 DW 1, 9078H, ?
byte1 DB 21, 'World'
DD 12345678H
DATA1 ENDS
一个代码段的例子:
CODE1 SEGMENT
MOV AX, DATA1 ;把数据段DATA1的段值送AX
MOV DS, AX ;把AX的值送给DS,即:DS存储数据段的段值
…
MOV AX, 4C00H
INT 21H ;调用DOS功能,结束程序的运行
CODE1 ENDS
6.1.2 段寄存器的说明语句
在汇编语言源程序中可以定义多个段,每个段都要与一个段寄存器建立一种对应关系。建立这种对应关系的说明语句格式如下:
ASSUME 段寄存器名:段名[, 段寄存器名:段名, ……]
其中:段寄存器是CS、DS、ES、SS、FS和GS,段名是在段定义语句说明时的段名。
在一条ASSUME语句中可建立多组段寄存器与段之间的关系,每种对应关系要用逗号分隔。例如,
ASSUME CS:CODE1, DS:DATA1
上面的语句说明了:CS对应于代码段CODE1,DS对应于数据段DATA1。
在ASSUME语句中,还可以用关键字NOTHING来说明某个段寄存器不与任何段相对应。下面语句说明了段寄存器ES不与某段相对应。
ASSUME ES:NOTHING
在通常情况下,代码段的第一条语句就是用ASSUME语句来说明段寄存器与段之间的对应关系。在代码段的其它位置,还可以用另一个ASSUME语句来改变前面ASSUME语句所说明的对应关系,这样,代码段中的指令就用最近的ASSUME语句所建立的对应关系来确定指令中的有关信息。
例6.1 汇编语言段及其段说明语句的作用。
DATA1 SEGMENT ;定义数据段DATA1
word1 DW 5678h
byte1 DB "ABCDEFG"
DATA1 ENDS
DATA2 SEGMENT ;定义数据段DATA2
word2 DW 1234h
word3 DW 9876h
DATA2 ENDS
DATA3 SEGMENT ;定义数据段DATA3
byte2 DB ?
DATA3 ENDS
CODE1 SEGMENT ;编写代码段CODE1
ASSUME CS:CODE1, DS:DATA1, ES:DATA2 ;(1)
MOV AX, DATA1 ;(2)
MOV DS, AX ;(3)
MOV AX, DATA2 ;(4)
MOV ES, AX ;(5)
…
MOV AX, word1 ;访问段DATA1中的字变量word1
MOV word2, AX ;访问段DATA2中的字变量word2
…
ASSUME DS:DATA3, ES:NOTHING ;(6)
MOV AX, DATA3
MOV DS, AX
MOV BL, byte2 ;访问段DATA3中的字节变量byte2
…
MOV AX, 4C00H ;(7)
INT 21H ;(8)
CODE1 ENDS
语句(1)和(6)分别说明了段和段寄存器之间的对应关系,其中语句(6)重新说明语句(1)所指定的对应关系。
二组语句(2)和(3)、(4)和(5)实现对段寄存器DS和ES赋初值。ASSUME说明语句只起说明作用,它不会对段寄存器赋值,所以,必须对有关段寄存器赋值。在以后的其它源程序中也都是用此方法来实现对数据段寄存器赋值的。
语句(7)和(8)是调用中断21H的4CH号功能来结束本程序的执行,程序的返回代码由寄存器AL来确定。结束本程序执行的指令是所有主模块必须书写的语句。
注意:代码段寄存器不能由程序员在源程序中对其赋值,其值是由操作系统在装入它进入系统运行时自动赋值的。
6.1.3 堆栈段的说明
堆栈段是一个特殊的段,在程序中可以定义它,也可以不定义。除了要生成COM型执行文件的源程序外,一个完整的源程序一般最好定义堆栈段。如果在程序中不定义堆栈段,那么,操作系统在装入该执行程序时将自动为其指定一个64K字节的堆栈段。
在程序没有定义堆栈段的情况下,在由连接程序生成执行文件时,将会产生一条如下的警告信息,但程序员可以不理会它,所生成的执行文件是可以正常运行的。
warning xxxx: no stack segment (其中:xxxx是错误号)
在源程序中,可用以下方法来定义堆栈段。
方法1:
STACK1 SEGMENT
DB 256 DUP(?) ;256是堆栈的长度,可根据需要进行改变
TOP LABEL WORD
STACK1 ENDS
以上堆栈段的定义如图6.1所示。由于堆栈是按地址从大到小的存储单元顺序来存放内容的,所以,在堆栈存储单元说明语句之后,再说明一个栈顶别名,这样,对栈顶寄存器SP的赋值就很方便。
在源程序的代码段中,还要添加如下程序段,才能把段STACK1当作堆栈段来使用。
图6.1 堆栈段定义示意图
…
ASSUME SS:STACK1 ;可在代码段的段指定语句中一起说明
CLI ;禁止响应可屏蔽中断
MOV AX, STACK1
MOV SS, AX
MOV SP, offset TOP ;给堆栈段的栈顶寄存器SP赋初值
STI ;恢复响应可屏蔽中断
…
方法2:
STACK1 SEGMENT STACK ;定义一个堆栈段,其段名为STACK1
DB 256 DUP(?)
STACK1 ENDS
上述段定义说明了该段是堆栈段,系统会自动把段寄存器SS和栈顶寄存器SP与该堆栈段之间建立相应的关系,并设置其初值,而不用在代码段对它们进行赋值。
除了以上二种方法外,还有一种更简洁的方法来定义堆栈段,有关内容请见第6.4.2节中的叙述。
6.1.4 源程序的结构
下面的程序是一个完整的源程序,其功能是在屏幕上显示字符串“Hello, World.”。读者可参考此结构编写自己的简单程序。
例6.2 在屏幕上显示字符串“HELLO,WORLD.”
解:可运行下面的控件,用鼠标左键单击程序中的某一行,可阅读其含义;单击“内存”可切换内存内容的显示方式。
伪指令END表示源程序到此为止,汇编程序对该语句之后的任何内容都不作处理,所以,通常情况下,伪指令END是源程序的最后一条语句。
伪指令END后面可附带一个在程序中已定义的标号,由该标号指明程序的启动位置。
如果源程序是一个独立的程序或主模块,那么,伪指令END后面一定要附带一个标号;如果源程序仅是一个普通模块,那么,其END后面就一定不能附带标号。
6.2 程序的基本结构
在学习高级语言程序设计时,我们知道了程序的三大主要结构:顺序结构、分支结构和循环结构。在汇编语言的源程序也同样有此三大结构,所不同的是它们的表现形式不同。用高级语言编写程序时,由于不使用“转移语句”而使这三种结构清晰明了。
但在汇编语言的源程序中,很难不使用“转移语句”(除非是一些只有简单功能的程序),有时甚至会有各种各样的“转移语句”。由于存在这些转移语句,就使得:汇编语言源程序的基本结构显得不太明确。如果源程序的编写者思维混乱,编写出来的源程序在结构上就会显得杂乱无章,反之,如果编写者条理清晰,安排的操作井然有序,那么,编写出来的程序在结构上就会一目了然。
总之,不论是高级语言的源程序,还是汇编语言的源程序,其程序的三大基本结构也还是万变不离其宗的。
6.2.1 顺序结构
顺序结构是最简单的程序结构,程序的执行顺序就是指令的编写顺序,所以,安排指令的先后次序就显得至关重要。另外,在编程序时,还要妥善保存已得到的处理结果,为后面的进一步处理直接提供前面的处理结果,从而避免不必要的重复操作。
例6.3 编写程序段,完成下面公式的计算(其中:变量X和Y是32位有符号数,变量A,B和Z是16位有符号数)。
A←(X-Y+24)/Z的商,B←(X-Y+24)/Z的余数
解:
DATA1 SEGMENT
X DD ?
Y DD ?
Z DW ?
A DW ?
B DW ?
…
DATA1 ENDS
CODE1 SEGMENT
…
MOV AX, X
MOV DX, X+2 ;用(DX:AX)来保存32位变量X的数值
SUB AX,Y
SBB DX, Y+2 ;(DX:AX)-(Y+2:Y)
ADD AX, 24D
ADC DX, 0 ;(DX:AX)+24
IDIV Z
MOV A, AX
MOV B, DX
…
CODE1 ENDS
在编程序时,常常需要交换二变量之值。假设需要交换值的变量名为:var1和var2,临时增加的变量名为temp。常用的算法如下:
temp = var1
var1 = var2
var2 = temp
例6.4 假设有二个字变量word1和word2,编写程序段实现交换其值的功能。
解:
方法1:用汇编语言指令简单“直译”上面的 交换数据方法
DATA1 SEGMENT
…
word1 DW ?
word2 DW ?
temp DW ?
…
DATA1 ENDS
CODE1 SEGMENT
…
MOV AX, word1
MOV temp, AX ;上二语句实现语句“temp=word1”
MOV AX, word2
MOV word1, AX ;上二语句实现语句“word1=word2”
MOV AX, temp
MOV word2, AX ;上二语句实现语句“word2=temp”
…
CODE1 ENDS
这种方法虽然也能完成功能,但显然其不能充分利用汇编语言的特点,程序效率很低。
方法2:用汇编语言指令的特点来直接编译
DATA1 SEGMENT
…
word1 DW ?
word2 DW ?
…
DATA1 ENDS
CODE1 SEGMENT
…
MOV AX, word1
XCHG AX, word2
MOV word1, AX ;能XCHG word1, word2来代替这三条指令吗?
…
CODE1 ENDS
该方法充分利用了汇编语言的特点,不仅省去了中间变量temp的定义,而且程序的效率也提高了。
6.2.2 分支结构
分支结构是一种非常重要的程序结构,也是实现程序功能选择所必要的程序结构。由于汇编语言需要书写转移指令来实现分支结构,而转移指令肯定会破坏程序的结构,所以,编写清晰的分支结构是掌握该结构的重点,也是用汇编语言编程的基本功。
在程序中,当需要进行逻辑分支时,可用每次分二支的方法来达到程序最终分多支的要求,也可是用地址表的方法来达到分多支的目的。
一、显示转移指令实现的分支结构
在高级语句中,分支结构一般用IF语句来实现,在汇编语言中,课用无条件转移指令或条件转移指令实现的分支结构。如图6.2给出了二种常用的分支结构。
在编写分支程序时,要尽可能避免编写“头重脚轻”的结构,即:当前分支条件成立时,将执行一系列指令,而条件不成立时,所执行的指令很少。这样就使后一个分支离分支点较远,有时甚至会遗忘编写后一分支程序。这种分支方式不仅不利于程序的阅读,而且也不便将来的维护。
所以,在编写分支结构时,一般先处理简单的分支,再处理较复杂的分支。对多分支的情况,也可遵循“由易到难”的原则。因为简单的分支只需要较少的指令就能处理完,一旦处理完这种情况后,在后面的编程过程中就可集中考虑如何处理复杂的分支。
(a) if … endif结构 (b) if…else…endif结构
图6.2 分支结构的二种结构
例6.5 已知字节变量CHAR1,编写一程序段,把它由小写字母变成大写字母。
解:
DATA1 SEGMENT
…
CHAR1 DB ?
…
DATA1 ENDS
CODE1 SEGMENT
…
MOV AL, CHAR1
CMP AL, ‘a’
JB next
CMP AL, ‘z’
JA next
SUB CHAR1, 20H ;指令AND CHAR1, 0DFH也可以
next: …
…
CODE1 ENDS
例6.6 编写一程序段,计算下列函数值。其中:变量X和Y是有符号字变量。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -