第4章 伪指令与源程序格式汇总
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第4章伪指令与源程序格式
汇编语言程序的语句有三种,即指令、伪指令,还可以有宏指令。
关于宏指令将在第7章介绍,本章介绍部分常用的伪指令(又称伪操作)。
这些伪指令在程序中是必不可少的,主要用来定义数据变量和程序结构。
本章还介绍指令中的操作数和运算符,通过本章的学习,可以学会使用简便而有效率的指令格式,正确定义数据变量,熟知源程序的格式,编写完整的汇编语言程序。
4.1 伪指令
伪指令和指令不同的是,指令是在程序运行期间由计算机的CPU来执行的,而伪指令是在汇编程序对源程序进行汇编期间由汇编程序处理的操作。
它们可以完成如定义数据、定义程序模式、分配存储区、指示程序结束、处理器选择等功能。
这里只介绍一些常用的伪指令。
有些和宏汇编有关的伪指令在介绍宏汇编时再作说明。
4.1.1 处理机选择伪指令
由于80x86的所有处理器都支持8086指令系统,但每一种高档的机型又都增加了一些新的指令。
为了能使用这些新增指令,在编写程序时要用处理机选择伪指令对所用的处理机作出选择,也就是说,要告诉汇编程序应该选择哪一种指令系统。
处理机选择伪指令有以下几种:
.8086 选择8086指令系统
.286 选择8O286指令系统
.286P 选择保护方式下的80286指令系统
.386 选择80386指令系统
.386P 选择保护方式下的8O386指令系统
.486 选择80486指令系统
.486P 选择保护方式下的8O486指令系统
.586 选择Pentium指令系统
.586P 选择保护方式下的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赋data段基地址
mov ds,ax
mov dx,offset string
mov ah,9
int 21h
mov ah,4ch
int 21h
code ends
end start ;汇编结束, 程序起始点start:
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 指令完成这一操作。
但是,代码段不需要这样做,代码段的这一操作是在程序初始化时完成的。
一般情况下,使用上述的段定义伪指令就可以了,如果需要对段定义作进一步地控制,SEGMENT伪指令还可以增加类型及属性的说明,其格式如下:
segment_name SEGMENT [定位类型][组合类型][ 使用类型][“类别”]
…
segment_name ENDS
如果需要用连接程序把本程序与其他程序模块相连接时,就需要使用这些说明,具体内容安排在第6章有关子程序的多模块设计中介绍。
2.简化的段定义伪指令
MASM5.0以上版本还支持一种简化的段定义方法,把例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
mov ah,4ch
int 21h
end start
首先用.MODEL伪指令说明在内存中如何安排各个段,存储模型为SMALL的意思是:所有数据都放在一个64KB的数据段,所有代码都放在另一个64KB的代码段,数据和代码都为近访问。
这是最常用的一种模型。
.DATA伪指令用来定义数据段,但没有给出段名,默认段名是_DATA。
@DATA表示段名_DATA,在指令中表示段地址。
简化段定义的表达能力不如SEGMENT伪指令那样完整而清楚,所以很多时候还是用SEGMENT伪指令。
有关简化段定义的更多说明在第6章有关子程序的多模块设计中介绍。
4.1.3 程序开始和结束伪指令
在前面例子中,都没有使用表示程序开始的伪指令。
用户根据需要可以在程序的开始用NAME或TITLE伪指令定义该程序模块名。
NAME的格式为:
NAME module_name
其中module_name 为模块的名字。
如果程序中没有使用NAME伪指令,也可使用TITLE 伪指令来指定模块名,其格式为:
TITLE text
其中text中的前六个字符被汇编程序作为模块的名字。
TITLE伪指令的另一个作用是在列表文件的每一页上打印标题。
标题text最多可有6 0个字符。
如果程序中既无NAME又无TITLE伪指令,则用源文件名作为模块名。
所以NAME及TITLE 伪指令不是必要的。
表示源程序结束的伪操作的格式为:
END [label]
汇编程序将在遇到END时结束汇编。
其中标号label指示程序开始执行的起始地址。
如果是多个程序模块相连接,则只有主程序需要使用标号,其他子程序模块则只用END而不能指定标号。
4.1.4 数据定义与存储器单元分配伪指令
我们知道,指令语句的一般格式是:
[标号:] 操作码操作数 [;注释]
这一类伪指令的格式是:
[变量] 操作码 N个操作数 [;注释]
其中变量字段是可有可无的,它用符号地址表示。
其作用与指令语句前的标号相同。
但它的后面不跟冒号。
操作码字段说明所用伪操作的助记符,即伪操作,说明所定义的数据类型。
常用的有以下几种:
DB 伪操作用来定义字节,其后的每个操作数都占有一个字节(8位)。
DW 伪操作用来定义字,其后的每个操作数占有一个字(16位,其低位字节在第一个字节地址中,高位字节在第二个字节地址中,即数据低位在低地址,数据高位在高地址)。
DD 伪操作用来定义双字,其后的每个操作数占有两个字(32位)。
DF 伪操作用来定义6个字节的字,其后的每个操作数占有48位。
DQ 伪操作用来定义4个字,其后的每个操作数占有4个字(64位),可用来存放双精度浮点数。
DT 伪操作用来定义1O个字节,其后的每个操作数占有1O个字节,为压缩的BCD码。
(需要说明的是,MASM6允许DB,DW,DD,DF,DQ,DT伪操作分别用BYTE,WORD,DWORD,FWORD,QWORD,TBYTE代替)。
这些伪操作可以把其后跟着的数据存人指定的存储单元,形成初始化数据;或者只分
配存储空间而并不确定数值。
下面举例说明各种用法。
例4.3 操作数为常数、数据表达式。
D_BYTE DB 10,5,10H
D_WORD DW 14,100H,-5,0ABCDH
D_DWORD DD 4×8
程序中默认的数据为十进制数,10H为十六进制数,用DB定义的数据的值不能超出一个字节所能表示的范围。
数据10的符号地址是D_BYTE,数据5的符号地址是D_BYTE+1。
数据可以是负数,均为补码形式存放。
允许数据表达式,如4×8,等价为32。
当数据第一位不是数字,应在前面加0,如0ABCDH。
数据在内存中的存放如图4.1所示。
D_BYTE D_WORD D_DWORD
图4.1例4.3的汇编结果
例4.4 操作数为字符串。
问号‘?’仅预留空间。
数据在内存中的存放如图4.2所示。
MESSAGE DB 'HELLO',?
DB ‘ABCD’
MESSAGE
图4.2 例4.4的汇编结果
例4.5 用操作符复制操作数。
数据在内存中的存放如图4.3所示。
ARRAY DB 2 DUP(1,3,2 DUP(4,5))
ARRAY
图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
图4.4 (1) 例4.8的数据定义
OPR_B
OPR_W
图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伪操作的引入提高了程序的可读性,也更加易于程序的修改。
必须注意:在EQU语句的表达式中,如果有变量或标号的表达式,必须先定义后引用。
另一个更为简洁的赋值伪操作是=,格式同EQU,只是用=替换EQU。
它们之间的区别是EQU伪操作中的表达式名是不允许重复定义的,而=伪操作则允许重复定义。
例如,VAL=53
VAL=VAL+53
VAL可以多次被伪操作=赋值,而EQU则不允许重复定义。
4.1.6 汇编地址计数器与定位伪指令
1.地址计数器$
在汇编程序对源程序汇编的过程中,为了按序存放程序中定义的数据变量和指令,使用16位的地址计数器(location counter)来保存当前正在汇编的指令的偏移地址。
当开始汇编或在每一段开始时,把地址计数器初始化为零,以后在汇编过程中,每处理一条指令,地址计数器就增加一个值,此值为该指令所需要的字节数。
地址计数器的值可用$来表示,汇编语言允许用户直接用$来引用地址计数器的值。
如在指令中引用$,JMP $+8的转向地址是本条指令的首地址加上8。
显然$+8必须是另一条指令的首地址,否则汇编程序将指示出错。
当$用在伪操作的参数字段时,它所表示的是地址计数器的当前值。
例4.9 考察$的作用,假定$初值=0,数据在内存中的存放如图4.5所示。
ARRAY DW 3,$+7,7
COU=$
NEW DW COU
ARRAY NEW
图4.5 例4.9的汇编结果
2. ORG伪操作
ORG伪操作用来设置当前地址计数器的值,其格式为:
ORG constant expression
如常数表达式的值为n,则ORG伪操作可以使下一个字节的地址为n。
例4.10 考察ORG伪操作,数据在内存中的存放如图4.6所示。
ORG 0
DB 3
ORG 4
BUFF DB 5
ORG $+6
VAL DB 9
BUFF VAL
图4.6 例4.10的汇编结果
可以看成是从4号单元开始定义了一个名为BUFF长度为6个字节的键盘输入缓冲区。
3.EVEN伪操作
EVEN伪操作使下一个变量或指令开始于偶数地址。
一个字的地址最好从偶数地址开始。
例如:
EVEN
ARRAY DW 80 DUP(?)
4.ALIGN伪操作
ALIGN伪操作使下一个变量的地址从4的倍数开始,这可以用来保证双字数组边界从4的倍数开始,其格式为:
ALIGN boundary
其中Boundary必须是2的幂。
例如:
ALIGN 8
ARRAY DW 80 DUP(?)
4.1.7 基数控制伪指令
汇编程序默认的数为十进制数,所以在程序中使用其他基数表示的常数时,需要专门给以标记如下:
(1)二进制数:由一串0和1组成,其后跟以字母B,如00101001B。
(2)十进制数:由0~9的数字组成的数,一般情况下,后面不必加上标记,在指定了其它基数的情况下,后面跟字母D,例如23D。
(3)十六进制数:由0~9及A~F组成的数,后面跟字母H。
这个数的第一个字符
必须是0~9,所以如果第一个字符是A~F时,应在其前面加上数字0,如0FFFFH。
RADIX伪操作可以把默认的基数改变为2~16范围内的任何基数。
其格式
如下:
.RADIX expression
其中表达式用来表示基数值(用十进制数表示)。
注意:在用.RADIX把基数定为十六进制后,十进制数后面都应跟字母D。
在这种情况下,如果某个十六进制数的末字符为D,则应在其后跟字母H,以免与十进制数发生混淆。
4.1.8 过程定义伪指令
子程序又称过程,可以把一个程序写成一个过程或多个过程,这样可以使程序结构更加清晰,基本的过程定义伪指令的格式为:
procedure_name PROC Attribute
┇
procedure_name ENDP
其中过程名(procedure_name)为标识符,起到标号的作用,是子程序入口的符号地址。
属性(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命令状态。
这是一个固定用法。
RET指令使堆栈中的2个字(0和DS的值)弹出到IP和CS,实际上是执行了该处的退出程序的指令INT 20H。
增强功能的过程定义在第六章介绍。
但一般情况下,都是使用基本的过程定义。
4.2 语句格式
程序中用得最多的是指令和有关数据定义的伪指令,这些语句基本上可以由4项组成,格式如下:
[name] operation 0perand [;comment]
[名字] 操作操作数 [;注释]
名字项是一个符号,可以是指令的标号,也可以是变量名。
操作项是一个操作码的助记符,它可以是指令、伪指令或宏指令名。
操作数项由一个或多个表达式组成,它提供该操作所要求的操作数或相关信息。
注释项用来说明程序或语句的功能。
上面带方括号的两项是可有可无的,各项之间必须用‘空格’隔开。
下面分别说明各项的表示方法。
4.2.1 名字项和操作项
1.名字项
名字项用下列字符来表示:
字母A~Z
数字O~9
专用字符?,·,@,-,$
除数字外,所有字符都可以放在源语句的第一个位置。
名字中如果用到·,则必须是第一个字符。
名字项可以是标号或变量,用来表示本语句的符号地址。
如果是指令的标号,后面跟冒号:。
作为一个地址符号,显然应有3种属性:段、偏移及类型。
段属性,定义该地址符号的段起始地址,此值必须在一个段寄存器中。
偏移属性,偏移地址是从段起始地址到定义该地址符号的位置之间的字节数。
对于16位段是16位无符号数。
类型属性,对于标号,用来指出该标号是在本段内引用还是在其他段中引用的。
如是在段内引用的,则称为NEAR,对于16位段,指针长度为2字节。
如在段外引用,则称为FAR,对于16位段,指针长度为4字节(段地址2字节,偏移地址2字节)。
对于变量,类型属性定义该变量所保留的字节数。
如BYTE(1个字节)、WORD(2个字节)、DWORD(4个字节)、FWORD(6个字节)、QWORD(8个字节)、TBYTE(1O个字节),这一点在数据定义伪操作中已作了说明。
作为一个地址符号,在同一个程序中,显然不能重复定义,即同样的标号或变量的定义只允许出现一次,否则汇编程序会指示出错。
2.操作项
操作项可以是指令、伪指令或宏指令的助记符。
对于指令,汇编程序将其翻译为机器
语言指令。
对于伪指令,汇编程序将根据其所要求进行处理。
对于宏指令,则将根据宏定义展开。
有关宏指令的具体内容将在第7章专门介绍。
4.2.2 表达式和操作符
操作数项是指令的最复杂最灵活的一项。
操作数项由一个或多个表达式组成。
对于指令,操作数项一般给出操作数地址,它们通常不超过两个。
对于伪操作或宏指令,则给出它们所要求的参数。
操作数项可以是常数、寄存器、标号、变量。
这些我们已经知道。
操作数项还可以是表达式,而表达式是常数、寄存器、标号、变量与一些操作符(运算符)相组合的序列。
在汇编期间,汇编程序按照一定的优先规则对表达式进行计算后可得到一个数值或一个地址,如是数值,这个表达式就是数字表达式,如是地址,这个表达式就是地址表达式。
那么表达式有哪些操作符呢?下面介绍一些常用的操作符在表达式中的作用。
特别注意的是,表达式在汇编阶段起作用,只有正确的表达式才能通过汇编。
1.算术操作符
算术操作符有+,-,*,/和MOD。
其中MOD是指除法运算后得到的余数,如7/5的商为1,而7 MOD 5 为2(余数)。
要注意的是,算术操作符在表达式中的使用,其结果必须有明确的物理意义时才是有效的,下面举例说明:
例4.12算术操作符的使用
设有如下定义:
VAL=4
DA1 DW 6,2,9,3
DA2 DW 15,17,24
上面定义的VAL是常数,我们无需确定它的位置就可以使用。
DA1和DA2是变量的符号地址,它们在内存中有确定的位置,我们只有根据它们的地址才能访问。
MOV AX,DA1*4 ;错,地址乘或除,没有意义
MOV AX,DA1*DA2 ;错,地址乘或除,没有意义
MOV AX,DA1+DA2 ;错,地址相加,没有意义
MOV AX,BX+VAL ;错,BX+VAL须用指令实现
MOV AX,[BX+VAL] ;地址表达式,汇编成MOV AX,[BX+4]
MOV AX,DA1+VAL ;地址表达式,汇编成直接寻址指令,执行后,AX=9 MOV AX,VAL*4/2 ;数字表达式,汇编成MOV AX,8
MOV AX,[VAL*4/2] ;数字表达式,汇编成MOV AX,8
MOV CX,(DA2-DA1)/2 ;地址相减,得到DA1存储区长度,汇编成MOV CX,4 2.逻辑与移位操作符
逻辑操作符有AND,OR,XOR和NOT;移位操作符有SHL和SHR。
它们都是按位操作的。
只能用于数字表达式中。
逻辑操作符要求汇编程序对其前后两个操作数(或表达式)作指定的逻辑操作。
例4.13逻辑操作符的使用
VAL=4
MOV AX,BX AND 0FFH ;错,BX AND VAL须用指令实现
MOV AX,VAL AND 0F0H ;汇编成MOV AX,0
AND AX,VAL OR 0F0H ;汇编成AND AX,0F4H
移位操作符的格式是:
expression SHI(或SHR) numshift
汇编程序将expression 左移或右移numshift位,如移位数大于15,则结果为O。
例4.14移位操作符的使用
VAL=4
MOV AX,BX SHL 2 ;错,BX 左移须用指令实现
MOV AX,VAL SHL 2 ;汇编成MOV AX,10H
MOV AX,8 SHL 2 ;汇编成MOV AX,20H
3.关系操作符
关系操作符用来对两个操作数的大小关系作出判断。
六个关系操作符是EQ(相等)、NE(不等)、LT(小于)、GT(大于)、LE(小于等于)、GE(大于等于)。
关系操作符的两个操作数必须都是数字,或是同一段内的两个存储器地址。
计算的计算结果为逻辑值,如结果为真,表示为0FFFFH;结果为假,则表示为0。
例4.15关系操作符的使用
VAL=4
MOV AX,BX GT 2 ;错,BX 是否大于2须用指令实现判断
MOV AX,VAL GE 2 ;汇编成MOV AX,0FFFFH
MOV AX,8 LE VAL ;汇编成MOV AX,0
4.数值回送操作符
它主要有TYPE,LENGTH,SIZE,OFFSET,SEG等。
下面分别说明各个操作符的功能。
(1)TYPE
格式: TYPE expression
如果该表达式是变量,则汇编程序将回送该变量的以字节数表示的类型:DB为1,DW 为2,DD为4,DF为6,DQ为8,DT为10。
如果表达式是标号,则汇编程序将回送代表该标号类型的数值,NEAR为-1,FAR为-2。
如果表达式为常数则回送0。
(2) LENGTH
格式: LENGTH variable
变量用DUP复制的,则回送总变量数,其它为1。
(3) SIZE
格式: SIZE variable
变量用DUP复制的,则回送总字节数,其它为单个变量的字节数。
(4) OFFSET
格式: OFFSET variable或label
回送变量或标号的偏移地址。
(5) SEG
格式: SEG variable或label
回送变量或标号的段地址。
例4.16数值回送操作符的使用
设有如下定义:
ORG 0
VAL=4
ARR DW 4 DUP(3)
DAT DW 15,17,24
STR DB ‘ABCDEF’
汇编程序对下面的指令汇编结果为:
MOV AX,TYPE ARR ;汇编成MOV AX,2
MOV AX,LENGTH ARR ;汇编成MOV AX,4
MOV AX,LENGTH DAT ;汇编成MOV AX,1
MOV AX,SIZE ARR ;汇编成MOV AX,8
MOV AX,SIZE DAT ;汇编成MOV AX,2
MOV AL,SIZE STR ;汇编成MOV AX,1
MOV AX,OFFSET ARR ;汇编成MOV AX,0
5.属性操作符
它主要有PTR、段操作符、SHORT、THIS、HIGH、LOW、HIGHWORD和LOWWORD等。
这类操作符通常在指令执行时起作用。
(1)PTR
格式:type PTR expression
用类型type 对expression所表达的变量地址赋予另一种属性。
(2)段操作符,表示地址的段属性,如指令中使用的段前缀。
(3)SHORT,如JMP SHORT P1指令,表示转移地址为短转移。
(4)THIS
格式:THIS type
只是指定一个类型type的操作数,使该操作数的地址与下一个存储单元地址相同。
功能和LABEL伪指令类似。
(5) HIGH和LOW,HIGHWORD和LOWWORD
把一个字的高位/低位字节分离,把一个双字的高位/低位字分离。
例4.17属性操作符的使用
设有如下定义:
VAL=1234H
BUF=THIS BYTE
DAT DW 6,12,17
因为BUF和DAT的地址相同,但类型不同,则:
MOV AL,BUF 和
MOV AL,BYTE PTR DAT两条指令等效。
指令 MOV AH,HIGH VAL 执行后,AH=12H。
需要说明的是,在计算表达式时,根据操作符的优先级和括号,从左到右进行计算。
下面给出操作符的优先级别,从高到低排列,有11级:
(1)在圆括号中的项,方括号中的项,结构变量(变量,字段),LENGTH,SIZE,WIDTH,MASK。
(2)名:(段取代)。
(3)PTR,OFFSET,SEG,TYPE,THIS及段操作符。
(4)HIGH和LOW。
(5)乘法和除法:*,/,MOD,SHL,SHR。
(6)加法和减法:+,-。
(7)关系操作:EQ,NE.LT,LE,GT,GE。
(8)逻辑:NOT。
(9)逻辑:AND。
(10)逻辑:OR,XOR。
(11)SHORT
由于表达式和操作符的规定较多,编程时不必为了一味追求程序的简练而使表达式复杂化,以免出错。
4.3.1 EXE文件与COM文件
1.程序段前缀PSP
程序在执行前调入内存时,由DOS确定装入的起始地址,并在此处首先建立一个程序段前缀PSP(Program Segment Prefix),接着装入程序。
PSP为256个字节,其中包含很多信息,例如:
地址PSP:0存放的是INT 20H(机器码为CD20)。
INT 20H指令为程序返回的中断调用。
地址PSP:80H~PSP:0FFH处存放命令行参数,参数直接提供给可执行程序。
如执行程序PROG.EXE,可在DOS提示符下键入:
PROG par1,par2
这里par1和par2为参数,则命令行参数域为:0bh,“par1,par2”,0dh。
这里0bh 表示参数长度,0dh表示回车符。
2. EXE文件
前面介绍的源程序都是形成EXE文件的格式,EXE文件允许多个段,可以指定任一条指令为执行的开始地址。
EXE文件除了程序本身,还有文件头。
文件头由LINK程序生成,其中包括程序的重定位信息,供DOS装入文件时用。
例如,在程序中经常用这样的指令来确定段寄存器的值:
MOV AX,DATA
MOV DS,AX
程序在装入前无法确定在内存中的物理位置,LINK后的DATA段地址只是一个相对地址,在程序装入内存时,DOS根据装入的起始地址,把DATA段的相对地址转为绝对地址,再通过执行程序,把DATA段的段地址送DS寄存器。
EXE文件装入内存后,有关寄存器的值如下:
DS=ES=PSP段地址
CS:IP=程序执行的起始地址
SS:SP=堆栈段的栈底地址
3. COM文件
还有一种可执行文件,其扩展名为COM,COM文件由本身的二进制代码组成,它没有EXE 文件那样具有文件信息的标题区,在装入内存后,其内容不变,它占用的空间比EXE文件要小得多。
COM文件要求源程序只含一个代码段,即CS=DS=ES=SS,所以占用空间不超过64KB,程序中如有过程调用,类型应为NEAR。
COM文件要求程序从100H开始执行(前面256字节为程序段前缀)。
COM文件的源程序格式举例如下:
例4.18
;hello1.asm
code segment
assume cs:code
org 100h
main proc near ;也可为far
mov ax,cs
mov ds,ax
mov dx,offset string
mov ah,9
int 21h
mov ah,4ch ;退出程序
int 21h
string db ‘hello,world!$’
main endp
code ends
end main
例4.19
;hello2.asm
code segment
assume cs:code
org 100h
start:
mov ax,cs
mov ds,ax
mov dx,offset string
mov ah,9
int 21h
int 20h ;退出程序
string db ‘hello,world!$’
code ends
end main
例4.20
;hello3.asm
code segment
assume cs:code
org 100h
main proc far
push ds
xor ax,ax
push ax
mov ax,cs
mov ds,ax
mov dx,offset string
mov ah,9
int 21h
ret ;退出程序
string db ‘hello,world!$’
main endp
code ends
end main
程序需要定义成一个远过程,开始的三条指令把DS和0压入堆栈,程序最后的RET指令,把栈顶的两个字弹出到CS和IP,这就使CS:IP=DS:0,从而执行CS:IP(DS:0)处的指令,而这里正是PSP:0处,其中存放的是INT 20H(机器码为CD20)。
4. COM文件的产生
并不是具有COM文件的源程序格式,就一定是EXE文件,COM文件也是通过LINK连接程序产生,连接命令后面须加上/T。
如:
C:\MASM6\LINK HELLO/T
如果同一目录中有两个文件,如PROG.EXE和,当键入PROG执行程序时,COM 文件将被执行,键入PROG.EXE才能执行PROG.EXE。
习题4
4.1假设VAR1为字节变量,VAR2为字变量,LAB为标号,VAL为常量,指出下列有错误的指令,说出原因,并予纠正。
(1) ADD AX,VAR1
(2) MOV VAR2,VAR1
(3) MOV VAR1,BYTE PTR VAR2
(4) MOV VAR1,OFFSET VAR2
(5) MOV VAR2,OFFSET VAR1
(6) MOV VAR1,VAR2+VAR1
(7) MOV VAR1,VAR2-VAR1
(8) MOV VAR2,VAR2-VAR1
(9) MOV BX,VAR2+VAL-4*5
(10) MOV BX,TYPE VAL
(11) MOV BX,LAB
(12) JMP VAR1
(13) JMP VAR2
(14) JMP [VAL]
(15) MOV BL, VAR1 AND VAL
(16) MOV BX, VAL AND 0FH
(17) MOV BX, VAL LT 0FH
(18) MOV BL,LAB+VAR1
4.2画图说明下列数据定义语句所示内存空间的数据,并回答寄存器的值。
ORG 0
ARRAY LABEL BYTE
DA1 DW 2,9,14,3,315H,-6
DA2 DB 7,‘ABCDEDFG’
LEN = $-DA2
ORG 100H
DA3 DW DA4
DA4 DB 4 DUP(2 DUP(1,2,3),4)
MOV AL,ARRAY+2 (AL)=()H
ADD AL,DA2+1 (AL)=()H
MOV AX,DA2-DA1 (AX)=()H
MOV BL,LEN (BL)=()H
MOV AX, DA3 (AX)=()H
MOV BX, TYPE DA4 (BX)=()H
MOV BX, OFFSET DA4 (BX)=()H
MOV CX, SIZE DA4 (CX)=()H
MOV DX, LENGTH DA4 (DX)=()H
MOV BX, WORD PTR DA4 (BX)=()H
MOV BL, LEN AND 0FH (BL)=()H
MOV BL, LEN GT 5 (BL)=()H
MOV AX, LEN MOD 5 (AX)=()H
4.3 变量和标号有哪些区别?变量和标号有哪些属性?如何获取属性值?写出指令。
4.4 指令和伪指令的区别在哪里?伪指令可以出现在代码段吗?指令可以在数据段吗?4.5 下面的程序是否有错?能否通过汇编?程序运行的结果如何?程序的真正意图是什么?应如何修改程序才能实现真正意图?
CODE SEGMENT
ASSUME CS:CODE。