高级语言都规定了一系列用于编写程序的语句和语法规则。我们可以根据语言给定的语句及其语法规则编写程序,完成所要求的任务。汇编语言也一样有它的语句及其语法规则。但汇编语言与高级语言有较大的区别,汇编语言中的语句与机器的种类和型号密切相关。一条汇编语句对应一条机器指令。
本章主要内容
& 汇编语言的语句及语法规则
& 宏汇编的使用
& 伪指令的使用
源程序由语句组成。汇编语句一般可分为3部分,其一般形式为:
[名字]指令操作符[操作数1[,操作数2[,操作数3]]][;注释]
语句的第一部分是名字,它可能是标号,也可能是变量、段名或过程名;第二部分是指令操作符;第三部分是操作数;第四部分是注释。
汇编语言对大小写不敏感。为了区分保留字与标识符,本书中的例子将保留字用大写表示,标识符用非大写字母表示,以示区分。
【例10-1】以一个汇编程序源码介绍汇编语言格式。
stack1 SEGMENT SATCK 'stack'
stspace DW 100H DUP(?)
stack1 ENDS
data1 SEGMENT 'DATA'
z DB ?
x DB 10,4
data1 ENDS
code1 SEGMENT 'CODE'
ASSUME CS:code1,DS:data1,SS:stack1
Start: MOV AX,data1
MOV DS,AX
MOV AL,x
disp PROC
MOV DL ,AL
disp ENDS
code1 ENDS
END start
名字的定义类似于高级语言中的标识符(identifier)的规定,由用户给出。例10-1中stack1为堆栈段,data1为数据段,code1为程序段,disp为过程名,以冒号做间隔符的是标号,其余是变量名。
指令操作符表示语句的主要操作或功能,通常分为伪操作符与操作符两大类。
(1)伪操作符。
例10-1中出现的伪操作符可分为3种:完整段伪操作符、简化段伪操作符及其他伪操作符。
① 完整段伪操作符。
汇编源程序由分段组成,定义完整分段的伪操作符有3个。
SEGMENT:表示分段开始。其左边是段名,是由用户设置的标识符,不可省略。其右边是操作数,STACK操作数是堆栈段必须写的保留字。单引号括起来的操作数也是由用户设置的标识符。一般源程序必须有一个堆栈段,系统自动会把该段的段地址及段长赋给段寄存器SS和栈指针SP。
ENDS:表示分段结束,它的左边必须写该分段的段名。
ASSUME:指定各分段所用段寄存器的名字。
② 简化段伪操作符。
MASM汇编程序从5.0版开始引入了简化分段定义的伪操作符,从而省略了以上3个定义分段的伪操作符,使得源程序结构更为简单。凡只有一个数据段、一个堆栈段及一个代码段并且段长不超过64KB的源程序均可使用简化段源程序的结构。例10-1中定义的简化段伪操作符有下列4个。
MODEL:表示简化段采用的内存模式,即每个分段的长度范围,其右边的参数SMALL表示分段长度小于等于64KB。此伪操作符是简化段中必须有的,并且出现在源程序的最前面。
STACK:简化段的堆栈段伪操作符,它表示堆栈段开始,并且表示使用SS寻址该段。操作数表示堆栈段的长度,长度以字节为单位,系统自动把该长度赋给栈指针SP。
DATA:简化段的数据段伪操作符,它表示数据段开始,并且表示使用DS寻址该段。
CODE:简化段的程序段伪操作符,它表示该程序段开始,并且表示使用CS寻址该段。
以上4个伪操作符也表示前一个分段的结束,即一个分段的开始,就意味着前一个分段的结束。
③ 其他伪操作符。
例10-1中与分段无关的伪操作归结为其他伪操作,它们在简化段或非简化段中均可使用。例10-1中其他伪操作符有下列5个。
END:表示源程序的结束,其操作数可写上启动标号。这样系统就给CS、IP置初值。主程序必须写上启动标号,否则,程序有可能不会正确执行。
PROC:表示过程开始(即子程序开始),左边的过程名不可省略。
ENDP:表示过程结束,左边必须写上该过程的过程名。
DB:表示其左边为字节变量,右边为该变量的值。如果是成组变量,则各值用逗号分隔。其中“?”表示不确定的值。如果成组变量中各值相同,则可简写为DUP重复子句形式,例如变量中有5个相同的值“20H”,可写成“5 DUP(20H)”。
DW:表示其左边为字变量,其他规则类似DB。
(2)操作符。
例10-1中的MOV、ADD、SUB、CALL及RET等都是操作符,即它们和机器指令的操作码一一对应。由这些操作符组成的语句为执行语句。
(1)双操作数语句。
形式:[标号:]操作符 目标操作数,源操作数。
功能:目标操作数←(目标操作数)操作(源操作数)。
操作数可以是变量、寄存器、段名或数据。
例如,程序中的语句:
MOV AX,DATAl ; 把DATAl段的段地址赋给AX
SUB AL,y ; AL←(AL)-y
ADD AL,3 ; AL←(AL)+3
(2)单操作数语句。
形式:[标号:]操作符 操作数。
功能:对单个操作数进行操作符规定的操作。
这里的操作数同以上说明,例如程序中语句“CALL DISP”表示调用子程序DISP,语句“INT 21H”表示调用系统功能函数。当参数(AH)=2时,表示屏幕显示DI的内容;当参数(AH)=4CH时,表示程序结束,返回操作系统。
(3)无操作数语句。
形式:[标号:]操作符。
功能:执行操作符规定的操作。
例10-1中的RET语句就是无操作数语句,表示返回主程序。
通常一个语句占一行。以“;”开始的内容是注释,分号引入的注释还可以放在解释语句的句首。从例10-1可以看出,汇编语言源程序由分段组成。该程序有3个分段:第一个分段是堆栈段,第二个分段是数据段,第三个分段是程序段。
源程序中的符号有两类:一类是事先由汇编程序(也就是编译程序)约定的,它们不用定义与说明,也不能更改,称为保留字;另一类是用户定义的,与高级语言标识符的概念类同,汇编语言称为名字。
(1)寄存器名字的约定。
汇编中寄存器一律使用系统规定的名字,并且使用方式只有系统指令。
通用8位寄存器:AH、AL、BH、BL、CH、CL、DH、DI。
通用16位寄存器:AX、BX、CX、DX、BP、SP、SI、DI。
通用32位寄存器:EAX、EBX、ECX、EDX、EBP、ESP、ESI、EDI。
16位段寄存器:CS、DS、SS、ES。
(2)操作符的约定。
汇编语言的操作符表示该语句要执行的操作,操作符也是一种系统规定的名字,如MOV、OR、ADD等。机器中的操作符对应一个二进制代码,称为操作码。操作符是操作码的符号化表示。
(3)伪操作符的约定。
伪操作符就是一个伪操作的符号化表示。伪操作的功能由汇编系统实现,不是由CPU执行的操作也不会有对应的二进制代码。伪操作符也是一种系统规定的名字,不用规定就可使用。
汇编系统的功能强弱除受指令系统的限制外,主要反映在伪操作功能上。
汇编中的标号、变量、段名及过程名等都是由用户定义的名字,这里统一讲解名字的定义规则、常见名字的属性及使用。
定义名字必须遵守下列规则。
· 名字只能由字母A~Z、数字0~9、符号$、@、?、.、,、-字符组成。
· 数字不能作为名字的第一个字符。
· 名字的长度可达31个字符,超过部分的字符被系统忽略。
符合上面规定的字符组合并且不与任何保留字相同的字符串被认为是合法的名字,否则是非法的名字。常见的名字有以下几种。
(1)标号及其属性。
标号是由一个冒号“:”引入的名字,代表其定义位置的地址。标号总是属于某一个代码段,它代表所定义语句对应代码指令的开始地址。该地址形式为“该代码段的段地址:偏移地址”。
标号的3个属性如下所示。
① 标号的段值属性。
标号的段值属性就是标号所在段的段地址,因而标号的段值属性指的是一个地址数,通常称为标号的段值。标号的段值属性可写成如下形式:
SEG标号
如SEG start就是start所在代码段的段地址。
② 标号的偏移属性。
标号的偏移属性是指标号所处位置的偏移地址,故标号的偏移属性指的也是一个地址数,通常称为标号的偏移地址。标号的偏移属性可写成如下形式:
OFFSET标号
如OFFSET start就是start所在代码段的偏移地址。
③ 标号的类型属性。
标号有远标号(FAR)与近标号(NEAR)之分,这个特性称为标号的类型属性,其形式为:
TYPE标号
对该属性系统也定义了一个类型值。
若为近标号,则(TYPE标号)=-1;若为远标号,则(TYPE标号)=-2。
对某段来说,远标号表示该标号的定义不在本段,近标号表示该标号的定义在本段。因此,标号的类型是相对的。显然,段中用冒号定义的标号对该段来说都是近标号,而对其他代码段则为远标号。例如,程序中的语句:
JZ LB
JMP LA
LB及LA为近标号,因为“JZ LB”及“JMP LA”指令与LB、LA标号的定义在同一段。如不在同一段则需写成如下形式:
JMP FAR PTR LA
从上面的讲解可以看出,标号的默认类型为NEAR型,标号的类型是相对的,也就是说,标号的类型是在指令引用它时才确定下来的。
(2)变量及属性。
变量是用数据伪操作符定义的名字。一般数据用下列伪操作符定义。
DB——每个数据为1个字节。
DW——每个数据为1个字,即2个字节。
DD——每个数据为1个双字,即4个字节。
DQ——每个数据为8个字节。
DT——每个数据为10个字节。
变量有两重含义,它一方面代表所表示的变量的值,另一方面表示变量所占空间的首地址。在汇编语言中,单个变量与数组的定义一致。
例10-1中有下列语句:
stspace DW 100 DUP(?)
Z DB ?
X DB 10,4
其中,Z,X,stspace均为变量。Z为单个变量,定义了1个字节空间,Z代表此字节单元的地址,此变量没有赋值。X可看作数组变量,(X)=10,(X+1)=4,并且有地址X+1=Z。stspace也可看作数组变量,只是它是用单项重复子句“100 DUP(?)”定义数组变量,可理解为定义100个字单元的空间,stspace为数组的首地址。变量总是属于某一分段,它代表的地址形式同标号,形式为“所在段的段地址:所在段的偏移地址”。
变量的5个属性如下所示。
① 变量的段值属性。
变量的段值属性是指变量定义所在段的段地址。因此变量的段值属性指的是一个地址数,通常称为变量的段值。变量的段值属性可写成如下形式:
SEG变量
如SEG Z就是求Z所在段的段地址。
② 变量的偏移属性。
变量的偏移属性是指变量所处位置的偏移地址,故变量的偏移属性指的也是一个地址数,通常称为变量的偏移地址。变量的偏移属性可写成如下形式:
OFFSET变量
如OFFSET Z就是Z所在代码段的偏移地址。
③ 变量的类型属性。
变量所表示的数据元素的长度(以字节为单位)为变量的类型属性,其形式为:
TYPE变量
因而“TYPE变量”这个表达式的值是1~10之间的数字,如TYPE Z=1。
④ 变量的长度属性。
变量所表示的数据元素的个数为变量的长度属性,其形式为:
LENGTH变量
变量的元素个数只对单项的重复子句有意义,对其他变量,LENGTH变量=1。
例如,对例10-1有下列结果:
LENGTH Z=1
LENGTH X=1
LENGTH stspace=100
⑤ 变量的容量属性。
变量的容量属性是指变量表示的数据所占空间的字节和,其形式为:
SIZE变量
但是变量的容量属性也只对单项的重复子句有意义,实际上,SIZE变量的值也可用下式计算:
SIZE变量=(LENGTH变量)×(TYPE变量)
例如,对例10-1有下列结果:
SIZE X=1
SIZE Z=1
SIZE stspace=100×2=200
(3)段名。
用伪操作符SEGMENT定义的名字为段名。
段定义形式为:
段名 SEGMENT …
……
段名 ENDS
段名是所定义段的符号化段地址,所以对于例10-1,下列语句是等价的:
MOV AX,mydata
MOV AX,SEG Z
MOV AX,SEG X
(4)过程名。
用伪操作符PROC引入的名字称为过程名。
过程的定义形式为:
过程名PROC 类型
……
过程名ENDS
过程名与标号类似,只是远、近过程名的类型是在过程的定义时由“类型”参数决定,类型为FAR时为远过程,类型为NEAR时为近过程。过程名的默认类型为近过程。远过程名是过程所处位置首地址的符号化地址,表示形式为“段地址:偏移地址”。近过程名则是过程所处位置首地址的偏移地址的符号化偏移地址。
汇编语言中的常数可以分为数值常数、字符串常数、属性常数及当前地址常数等。
数值常数分为整数常数和实数常数。
(1)整数常数。
按其基数的不同,可以有二进制数、八进制数、十进制数、十六进制数等几种不同的表示形式,汇编语言中采用不同的后缀加以区分。
· B:表示二进制。例如,00110111B。
· D:表示十进制数。例如,上面二进制数的十进制表示形式是55D。
· Q:表示八进制数。例如,上面二进制数的八进制表示形式是67Q。
· H:表示十六进制数。例如,上面二进制数的十六进制表示形式是37H。
例子中的4个数据是以不同的基数形式表示的同一个数值。当一个数值数据后面没有后缀时,默认为十进制数。
(2)实数常数。
实数常数在计算机内的表示有十进制和十六进制两种。
① 十进制实数。
它是一个带小数点的十进制数或带方幂的十进制数。例5.89,2.0,5.1E6,其中5.1E6=5.1×106。
② 十六进制实数。
用R替换H做标记的十六进制实数,为实数在计算机内的表示形式。它与十六进制数一样,必须以数字做首字符。例如:
42C88000R是IEEE格式编码的短实数100.25在计算机内的表示。0C1C90000R是IEEE格式编码的短实数-25.125在计算机内的表示。
显然,42C88000R与42C88000H、0CIC90000R与0C1C90000H在内存中的形式是一样的,引入R做标记的目的只是表示该数是实数的编码。
字符串常数是由单引号“‘’”括起来的一串字符。例如:
‘Assembly Language and Programmin9’
‘23’
需要指出的是,此处的‘23’并不表示十进制数23,而是2、3两个数值的ASCII码,即32H、33H。
最后还应指出,汇编语言中的数值常数的第一位必须是数字,否则汇编时将被看成是标识符。例如十六进制数ABCDH应表示成0ABCDH。
以上讲的名字的属性都可以作为常数使用。
(1)段值。
形式:SEG标号或变量。
取标号或变量的段地址为数值。
(2)偏移值。
形式:OFFSET标号或变量。
取标号或变量的偏移地址为数值。
(3)变量类型值。
形式:TYPE变量。
取变量或标号类型值为数值。
(4)变量长度值。
形式:LENGTH变量。
取变量元素的个数为数值,它只对用单项重复(DUP)子句定义的变量有意义。
(5)字节总和值。
形式:SIZE变量。
取变量总的字节数为数值,它也只对用单项重复(DUP)子句定义的变量有意义。
以上是汇编语言中允许书写的数据形式。它们必须被变成80x86或80x87在计算机内的数据形式,即二进制才能运行。如果二进制、十六进制、十进制数是有符号的,则在计算机内表示为补码二进制数,否则为无符号的二进制数。十进制实数在计算机内表示为80x87的实数。属性常数在计算机内表示为无符号二进制数,字符数在计算机内表示为ASCII码数。
汇编语言允许把某些语句所处内存的地址取出使用。一般有两种方式:一种是$代表当前位置的偏移地址;另一种是THIS指示当前位置的地址及其类型。
形式:THIS类型。
(1)类型为BYTE时,表示所处位置是字节变量的地址。
(2)类型为WORD时,表示所处位置是字变量的地址。
(3)类型为DWORD时,表示所处位置是双字变量的地址。
(4)类型为NEAR时,表示所处位置是本段标号的地址。
(5)类型为FAR时,表示所处位置是远标号地址。
它们的地址由下一个定义的内存变量语句决定,或者由下一条指令的地址决定。
例如,有下列语句:
ORG$+100
VC DB 1,2,3,4
其中,$为当前的偏移地址。此时变量VC的偏移地址就为当前地址加100,即将VC之前空出100字节空间。
例如,有下列语句:
va EQUTHIS BYTE
vb DW 50 DUP(?)
va与vb表示同一个内存空间100字节的首地址,但va是字节变量,vb是字变量。
例如,有下列语句:
tagfar EQUTHIS FAR
tagnear: MOV DS,AX
tagfar与tagnear都是语句“MOV DS,AX”所在空间的首地址,但tagfar是远标号,tagnear可能是近标号,也可能是远标号。