-
第
4
章
伪指令与源程序格式
汇编语言程序的
语句有三种,即指令、伪指令,还可以有宏指令。关于宏指令将在第
7
< br>章介绍,本章介绍部分常用的伪指令
(
又称伪操作
)
。这些伪指令在程序中是必不可少的,主
要
用来定义数据变量和程序结构。本章还介绍指令中的操作数和运算符,通过本章的学习,
可以学会使用简便而有效率的指令格式,正确定义数据变量,熟知源程序的格式,编写完整
的汇编语言程序。
4.1
伪指令
伪指令和指令不同的是,
p>
指令是在程序运行期间由计算机的
CPU
来
执行的,
而伪指令是
在汇编程序对源程序进行汇编期间由汇编程
序处理的操作。
它们可以完成如定义数据、
定义
程序模式、
分配存储区、
指示程序结束、
处理器选择等功能。
这里只介绍一些常用的伪指令。
有些和宏汇编有关的伪指令在介绍宏汇编时再作说明。
4.1.1
处理机选择伪指令
<
/p>
由于
80x86
的所有处理器都支持
p>
8086
指令系统,但每一种高档的机型又都增加了一些
新的指令。
为了能使用这些新增指令,
在编写程序
时要用处理机选择伪指令对所用的处理机
作出选择,也就是说,要告诉汇编程序应该选择
哪一种指令系统。
处理机选择伪指令有以下几种:
.8086
选择
8086
指令
系统
.286
选
择
8O286
指令系统
.286P
选择保护方式下的
80286
指令系统
.386
选择
80386
指
令系统
.386P
选择保护方式下的
8O386
指令系统
.486
选择
80
486
指令系统
.486P
选择保护方式下的
8O486
指令系统
.586
选择
p>
Pentium
指令系统
.586P
选择保护方式下的
< br>Pentium
指令系统
指令中的点‘
.
’是需要的。这类伪指
令一般放在整个程序的最前面。如不给出,则汇
编程序认为其默认选择是
8086
指令系统。
4.1.2
段定义伪指令
我们结合第
2
章已介绍的程序实例
2
来看段定义,
注意有分号的注释行,程序如下:
例
4.1
data
segment
;定义数据段
data
string db
‘
hello,world!
$$’
data
ends
code
segment
;定义代码段
code
assume
cs:code,ds:data
;指定段寄存器和段的关系
start
:
mov ax,data
;对
ds
赋
d
ata
段基地址
mov ds,ax
mov dx,offset string
mov ah,9
int
21h
mov ah,4ch
1
.段定义伪指令
汇编程序在把源程
序转换为目标程序时,必须确定标号和变量
(
代码段和数据段的
符号
地址
)
的偏移地址,连接程序对针
目标程序把不同的段和模块连接在一起,确定各个段的段
地址。段地址确定了,其中的指
令、标号和变量的段地址也就确定了,这样就形成一个可执
行程序。为此,需要段定义伪
指令。
段定义伪指令格式:
segment_name SEGMENT
?
segment_name
ENDS
其中
segment_name
由用户确定,大写的为关键字。段定义伪指令两句成对出现,两句
之间为其它指令。
为了确定用户定义的段和哪个段寄存器的关系,用
ASSUME
伪指令来实现。
ASSUME
伪指令格式:
ASSUME register_name:segment_name
?,
register_name:segment_name
其中
register_name
为段
寄存器名,
必须是
CS
,
DS
,
ES
和
SS
。
而
segment_
name
则必须是由
段定义伪指令定义的段中的段名。
ASSUME
伪指令只是指定把某个段分配给
哪一个段寄存器,
它并不能把段地址装入段寄存
器中,所以在代
码段中,还必须把段地址装入相应的段寄存器中。为此,还需要用两条
MOV
指令完成这一操作。但是,代码段不需要这样做
,
代码段的这一操作是在程序初始化时完成
的。
一般情况下,使用上述的段定义伪指令就可以了,如果需要对段定义作进一步地控制,
< br>SEGMENT
伪指令还可以增加类型及属性的说明,其格式如下:
segment_name SEGMENT [
定位类型
][
组合类型
]
[
使用类型
][
“类别”
]
?
segment_name ENDS
< br>如果需要用连接程序把本程序与其他程序模块相连接时,
就需要使用这些说明,<
/p>
具体内
容安排在第
6
章有关子程序的多模块设计中介绍。
2
.简化的段定义伪指令
MASM5.0
以上版本还支持一种简化的段定义方法,
< br>把例
4.1
程序用简化的段定义方法可以
改写如下:
例
4.2
.model small
;定义存储模型为
small
.data
;定义数据段
data
string db
‘
hello,world!
$$’
.code
;定义代码段
code
start
:
mov
ax,@data
;对
ds
赋
data
段基地址
mov ds,ax
mov dx,offset
string
mov ah,9
int 21h
code ends
end start
;汇编结束
,
程序起始点
start:
int 21h
mov ah,4ch
int 21h
end start
首先用
.MODEL
伪指令说明在内存中如何安排各个段,存储模型为
SMALL
< br>的意思是:所有
数据都放在一个
64KB
的数据段,
所有代码都放在另一个
64KB
的代码段,
数据和代码都为近访
问。这是最常用的
一种模型。
.DATA
伪指令用来定
义数据段,但没有给出段名,默认段名是
_DATA
。
@DATA
表示段名
_DATA
,在指令中表示段地址。
简化段定义的表达能力不如
SEGMENT
伪指令那样完整而清
楚,所以很多时候还是用
SEGMENT
伪指令。
有关简化段定义的更多说明在第
6
章有关子程序的多模块设计中介绍。
4.1.3
程序开始和结束伪指令
在前面例子中
,
都没有使用表示程序开始的伪指令。
用户根据需要可以在程序
的开始用
NAME
或
TITLE
伪指令定义该程序模块名。
NAME
的格式为
:
NAME module_name
其中
module_name
为模块
的名字。如果程序中没有使用
NAME
伪指令,也可使用
TITLE
伪指令来指定模块名,其格式为:
TITLE text
其中
tex
t
中的前六个字符被汇编程序作为模块的名字。
TITLE
伪指令的另一个作用是在列表文件的每一页上打印标题。标题
p>
text
最多可有
6
0
个字符。
如果程序中既无
NAME
又无
TITL
E
伪指令,
则用源文件名作为模块名。
所以
NAME
及
TITLE
伪指令不是必要的。
表示源程序结束的伪操作的格式为:
END [label]
汇编程序将在遇到
END
时结束汇编。
其中标号
label
指示程序开始执行的起始地址。
如
< br>果是多个程序模块相连接,
则只有主程序需要使用标号,
其他子程序模块则只用
END
而不能
指
定标号。
4.1.4
数据定义与存储器单元分配伪指令
我们知道,指令语句的一般格式是:
[
标号
:]
操作码
操作数
[
;注释
]
这一类伪指令的格式是:
[
变量
]
操作码
N
个操作数
[
;注释
]
其中变量字段是可
有可无的
,
它用符号地址表示。其作用与指令语句前的标号相同
。但
它的后面不跟冒号。
操作码字段
说明所用伪操作的助记符
,
即伪操作,说明所定义的数据类型。
常用的有以
下几种:
DB
伪操作用来定义字节,其后的每个操作数都占有一个字节
(8
位
)
。
DW
伪操作用来定义字,其后的每个操作数占有一个字
p>
(16
位,其低位字节在第一个字
节地址中
,高位字节在第二个字节地址中,即数据低位在低地址,数据高位在高地址
)
。
DD
伪操作用来
定义双字,其后的每个操作数占有两个字
(32
位
)
。
DF
伪操作用来定义
6
个字节的字,其后的每个操作数
占有
48
位。
DQ
伪操作用来定义
4
个字,其后的每个操作数占有
4
个字
(64
位
)
,可用来存放双
精
度浮点数。
DT
伪操作用来定义
1O
个字节,其后的每个操作数占有
1O
个字节,为
压缩的
BCD
码。
< br>(
需要说明的是,
MASM6
允
许
DB
,
DW
,
DD
,
DF
,
DQ
,
DT
伪操作分别用
BYTE
,
WORD
p>
,
DWORD
,
F
WORD
,
QWORD
,
TBYTE
代替
)
。
这些伪操作可以把其后跟着的数据存人指定的存储单元,形成初始化
数据;或者只分
配存储空间而并不确定数值。下面举例说明各种用法。
例
4.3
操作数为常数、数据表达式。
D_BYTE DB 10,5,10H
D_WORD DW
14,100H,-5
,
0ABCDH
D_DWORD DD
4
×
8
程序中默认的数据为十进制数,
10H
为十六进制数,
p>
用
DB
定义的数据的值不能超出一个
字节所能表示的范围。数据
10
的符号地址是
D_BYTE
,数据
5
的符号地址是
D_BYTE+1
。
数据可以是负数,均为补码形式存放。允许数据表达式,如
4
×
8
,等价为
32
。当数据
第一位不是数字,应在前面加
0
,如
0ABCDH
。数据在
内存中的存放如图
4.1
所示。
D_BYTE D_WORD
D_DWORD
↓
↓
↓
0A
05
10
0E
00
00
01
FB
FF
CD
AB
20
00
00
00
图
4.1
例
4.3
< br>的汇编结果
例
4.4
操作数为字符串。问号‘
?
’仅预留空
间。数据在内存中的存放如图
4.2
所示。
MESSAGE DB 'HELLO',?
DB
‘
ABCD
’
MESSAGE
↓
43
45
4C
4C
4F
--
41
42
43
44
图
4.2
< br>例
4.4
的汇编结果
例
4.5
用操作符复制操作数。数
据在内存中的存放如图
4.3
所示。
ARRAY DB 2 DUP(1,3,2 DUP(4,5))
ARRAY
↓
01
03
04
05
04
05
01
03
04
05
04
05
图
4.3
例
4.5
的汇编结果
例
4.6
指令中使用隐含类型属性。
OPER1 DB ?, ?
OPER2
DW ?, ?
┇
MOV OPER1, 0
MOV OPER2, 0
MOV OPER2, AX
第一条指令将使
OPER1
字节单元清零,第二条指令将使
OPER2
字单元清零,因为
OPER2
为字类型变量,第三条指令两
个操作数类型一致,无需说明。
例
4.7
在指令中使用类型属性操作符指定操作数类型。
OPER1 DB 3, 4
OPER2
DW 5678H, 9
┇
MOV AX
,
OPER1
MOV BL, OPER2
MOV [BX],
0
前两条指令操作数类型不匹配,
第三条指令的目标操作数类
型不明确,
所以都是错误的。
解决的办法是可在指令中对操作数
类型作临时性指定,
以使操作数类型匹配和明确。
这三条
指令可改为:
MOV
AX
,
WORD PTR OPER1
MOV BL, BYTE PTR OPER2
MOV
BYTE PTR[BX], 0
使用类型属性操作符
WORD
PTR
,
BYTE PTR
可对操作
数类型进行重新指定。指令执行结
果:
AX=0403H
,
BL=78H
。
实际上一个变量也可以定义成不同类型,以方便使用。这可以用
LABEL
伪操作来定义,
格式为:
name LABEL type
例
4.8
把变量定义成不同类型,
指令中可灵活选用。指令执行结果如图
4.4
所示。
OPR_B LABEL BYTE
OPR_W DW 4 DUP(0)
┇
MOV AX,
1234H
MOV OPR_B, AL
MOV
OPR_W+2, AX
OPR_B
OPR_W
↓
00
00
00
00
00
00
00
00
图
4.4 (1)
例
4.8
的数据定义
OPR_B
OPR_W
↓
34
00
34
12
00
00
00
00
图
4.4 (2)
例
4.8
的指令执行结果
OPR_B LABEL BYTE
伪操作使得
OPR_B
和
OPR_W
指向同一个内存单元。
4.1.5
表达式赋值伪指令
汇编语言程序也允
许表达式,以方便程序设计。可以用赋值伪操作给表达式赋予一
个名字。其格式如下:
Expression_name EQU Expression
上式中的表达式必须是有效的操作数格式或有效的指令助记符,
此后,
程序中凡需要用
到该表达式之处,就可以用表达式名来代替了。举例如
下:
VAL EQU 86
DATA EQU VAL+5
ADDR EQU [BP+VAL]
此后,指令
MOV
AX
,
ADDR
就代表
MOV AX
,
[BP+86]
,可见,
EQU
< br>伪操作的引入提
高了程序的可读性,也更加易于程序的修改。
必须注意:在
EQU
语句的表达
式中,如果有变量或标号的表达式,必须先定义后引用。
另一个更为简洁的赋值伪操作是=,格式同
EQU
,只是用=替换
EQU
。它们之间的区别
是
EQU
伪操作中的表达式名是不允许重复定义的
,而=伪操作则允许重复定义。
例如,
VAL=53
VAL=VAL+53
VAL
可以多次被伪操作=赋值,而
EQU
则不允许
重复定义。
4.1.6
汇编地址计数器与定位伪指令
1
.地址计数器
$$
< br>在汇编程序对源程序汇编的过程中,
为了按序存放程序中定义的数据变量和指令,
使用
16
位的地址计数器
(location
counter)
来保存当前
正在汇编的指令的偏移地址。
当开始汇编
或在每一段开始时,把
地址计数器初始化为零,以后在汇编过程中,每处理一条指令,地址
计数器就增加一个值
,此值为该指令所需要的字节数。地址计数器的值可用
$$
来表示
,汇编
语言允许用户直接用
$$
来引用地
址计数器的值。
如在指令中引用
$$
,<
/p>
JMP $$+8
的转向地址是本
条指令
的首地址加上
8
。显然
$$+8
必须是另一条指令的首地址,否则汇编程序将指示出错。
当
$$
用在伪操作的参数字段时,它所表示的是地址计数器的当前值。
例
4.9
考察
p>
$$
的作用
,
假定<
/p>
$$
初值
=0
,数
据在内存中的存放如图
4.5
所示。
ARRAY DW 3,$$+7,7
COU=$$
NEW DW COU
ARRAY
NEW
↓
↓
03
00
09
00
07
00
06
00
图
4.5
例
4.9
的汇编结果
2.
ORG
伪操作
ORG
伪操作用来设置当前地址计数器的值,其格式为:
ORG constant expression
如常数
表达式的值为
n
,则
ORG
伪操作可以使下一个字节的地址为
n
。
例
4.10
考察<
/p>
ORG
伪操作
,
数据在内存中的存放如图
4.6
所示。
ORG 0
DB 3
ORG
4
BUFF DB 5
ORG $$+6
VAL DB 9
BUFF
VAL
↓
↓
03
--
--
--
05
--
--
--
--
--
--
09
图
4.6
例
4.10
的汇编结果
可以看成是从<
/p>
4
号单元开始定义了一个名为
BUFF<
/p>
长度为
6
个字节的键盘输入缓冲区。
p>
3
.
EVEN<
/p>
伪操作
EVEN
伪操作使下一个变量或指令开始于偶数地址。一个字的地址最好从偶数地址开始。
例如
:
EVEN
ARRAY DW
80 DUP(?)
4
.
ALIGN
伪操作
ALIGN
< br>伪操作使下一个变量的地址从
4
的倍数开始,这可以用来
保证双字数组边界从
4
的倍数开始,其格式为:
ALIGN boundary
其中
Boundary
必须是
2
的幂。例如:
ALIGN 8
ARRAY DW 80 DUP(?)
4.1.7
基数控制伪指令
汇编程序默认的数为
十进制数,
所以在程序中使用其他基数表示的常数时,
需要专门
给
以标记如下:
(1)
p>
二进制数:由一串
0
和
1
组成,其后跟以字母
B
,如
p>
00101001B
。
(2)
十进制数:由
0
~
9
的数字组成的数,一般情况下,后面不必
加上标记,在指定了其
它基数的情况下,后面跟字母
D
,例如
23D
。
p>
(3)
十六进制数:由
0
~
9
及
A
~
F
组成的数,后面跟字
母
H
。这个数的第一个字符
必须是
0
~
9
,所以如果第一个字符是
A
~
< br>F
时,应在其前面加上数字
0
,
如
0FFFFH
。
RADIX
伪操作可以把默认的基数改变为
2
~
16
范围内的任何基
数。其格式
如下:
.RADIX expression
其中表达式用来表示
基数值
(
用十进制数表示
)
。
注意:
p>
在用
.RADIX
把基数定为十六进制后,
十进制数后面都应跟字母
D
。
在这种情况下,
如果某个十六进制数的末字符为
D
,则应在其后跟字母
H
,以免与十进
制数发生混淆。
4.1.8
过程定义伪指令
子程序又称过程,<
/p>
可以把一个程序写成一个过程或多个过程,
这样可以使程序结构更
加
清晰,基本的过程定义伪指令的格式为:
procedure_name PROC Attribute
┇
procedure_name
ENDP
其中过程名
(procedure_name)
p>
为标识符,起到标号的作用,是子程序入口的符号地址。
属性
(Attribute)
是指类型属性,可以是
NEAR
或
FAR
。例
4.1
的程序段可以改写为过程,见
下例:
例
4.11
data segment
;定义数据段
data
string db
‘
hello,world!
$$’
data ends
code segment
;定义代码段
code
assume
cs:code,ds:data
main proc
far
;定义过程
main
mov ax,data
mov ds,ax
mov dx,offset
string
mov ah,9
int 21h
mov ah,4ch
int 21h
main endp
code ends
end main
;汇编结束
,
程序起始点
main
该程序的过程部分也可写成:
main proc far
push ds
;
ds
进栈
mov ax
,
0
;
0
进栈
push ax
mov ax,data
mov ds,ax
mov dx,offset
string
mov ah,9
int 21h
ret
;返回
main endp
该过程对
DOS
来说是一个远过程,这里在过程的开始把当前
DS
的值和
0
压入堆栈,过
程的最后用
RET
返回到
DOS
p>
命令状态。这是一个固定用法。
RET
指令
使堆栈中的
2
个字
(0
和
DS
的值
)
弹出到
IP
和
CS,
实际上是执行了该处的退出程序的指令
INT
20H
。
增强功能的过程定义在第六
章介绍。但一般情况下,都是使用基本的过程定义。
4.2
语句格式
程序中用得最多的是指令和
有关数据定义的伪指令,这些语句基本上可以由
4
项组成,
p>
格式如下:
[name]
operation 0perand
[
;
comment]
[
名字
]
操作
操作数
[
;注释
]
名字项是一个符号,可以是指令的标号,也可以是变量名。
操作项是一个操作码的助记符,它可以是指令、伪指令
或宏指令名。
操作数项由一
个或多个表达式组成,它提供该操作所要求的操作数或相关信息。