微型计算机原理及应用学习笔记 汇编语言的基本
这一节主要介绍编写一个汇编语言源程序应具备的基本知识、基本规定和基本方法。
一、汇编语言程序的格式
(一) 基本概念
程序是为实现某一特定目的(例如对数据进行某种处理)而编写的一组指令有序集 合。汇编语言程序就是用汇编语言编写的源程序。汇编语言是一种面向机器的语言,它是与计算机硬件密切关连的,因而熟悉计算机硬件是汇编语言程序员必须具备的条件。与用高级语言编写的程序相比较,汇编语言程序具有更高的效率,它的程序执行时间短且占用内存少,这在计算机实时控制和实时处理中是十分重要的,因而在实时领域得到广泛应用。用汇编语言编写的源程序,必须由汇编程序(一种系统软件)进行汇编,将它转换成用机器语言表示的目标程序后,才能由CPU识别执行。因此编制程序时必须遵循规定的格式和语法,这是本章讨论的主要内容之一。对于不同型号的CPU和不同版本的汇编程序,其汇编语言是不同的。对于同一系列的CPU,则是向上兼容的。本书是针对8086/8088CPU进行讨论的。
(二)汇编语言源程序的特点和格式
下面列举了一个汇编语言程序,其功能是对10个字节数据a1 ~ a10求和。
DATA SEGMENT AT 2000H
ARRAY DB A1,A2,A3,…,A10
COUNT EQU $-ARRAY
SUM DW ?
DATA ENDS
STACK SEGMENT PARA STACK ‘STACK’
STAK DB 100 DUP(?)
TOP EQU LENGTH STAK
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START:MOV AX,DATA
MOV DS,AX
MOV AX,0
MOV DI,OFFSET SUM
MOV BX,OFFSET ARRAY
MOV CX,COUNT
LOP: ADD AL,[BX]
ADC AH,0
INC BX
LOOP LOP
MOV [DI],AX
MOV AH,4CH
INT 21H
CODE ENDS
END START
此例明显地示出了汇编语言程序的两个组成特点:分段结构和浯句行。
1.分段结构
在第二章中,我们已讨论了8086/8088的程序分段,知道程序最多可由4段组成,并 分别由段寄存器CS、DS、ES和SS的内容作为段基值,每段所占内存容量最大可达64KB。
上例程序共有3段,它们分别是数据段(段名DATA)、堆栈段(段名STACK)和代码段(段名CODE),各段由命令SEGMENT开始,并由命令ENDS结束。
2.语句行
上例程序共有26行,每行1个语句,即共有26个语句行。汇编语言程序的语句有两类:指令性语句和指示性语句。
(1)指令性语句。指令性语句是主要由指令构成的语句,其格式为
[标号:] 操作码 [操作数] [;操作数] [;注释];
其中操作码和操作数是用助记符表示的指令的两个部分,8086/8088的指令系统已在 第三章中讨论过,此处不再赘述。
其中带方括号的三项(标号操作数和注释)是任意选用的项,即根据具体编程需要该 项可有或没有,当然在实际语句中该项的方括号是不写出来的。标号具有该语句指令所在 内存地址的属性,通常在转移指令中用作目的地址。注意,标号必须用冒号“:”结尾,这是语法的规定。用分号“;”开始的注释用来说明该语句在程序中的作用,以方便程序的阅读和修改,这项也是任选的。
如上例的第18语句行
LOP:ADD AL,[BX]
其中指令是ADD AL,[BX],标号是LOP。LOP在第21语句行指令LOOP LOP中是转移的目的地址。又此语句中未用注释项。
(2)指示性语句。指示性语句是主要由命令(亦称伪指令)构成的语句,是用来指示汇编程序进行汇编操作的,其格式为:
[名字/变量] 命令 [参数] [;注释]
其中命令指示汇编程序进行某种汇编操作,参数是有关的数据,带方括号的项是任选 的。
如上例的第1语句行
DATA SEGMENT AT 2000H
其中命令SEGMENT指出这是一个段的开始,参数AT 2000H指定该段的段基值为 2000H,而DATA则是该段的名字。
又如上例的第5语句行
DATA ENDS
则表明了段名为DATA的段的结束。指示性语句的应用使程序员编程时不需进行很多计算,既方便又简化了编程工作。
二、常量、标识符和表达式
常量、标识符和表达式在汇编语言源程序中经常用到,本节对它们分别作一介绍。
(一)常量
常量是具有一定数值的量,汇编语言程序中的常量有:数字常量、字符常量和符号常量:
1.数字常量
数字常量可用二进制数、八进制数、十进制数或十六进制数表示。所用的数制要用后 缀示明:二进制为B、八进制为Q、十进制为D、十六进制为H。对于十进制数可以用后缀D,也可以不用后缀。对于十六进制数,若最高位的数为A-F,须在它的前面加上数字0,以表明是数值数据。
例如:10100110B,246Q,166D,166,6AH,0A6H。
2.字符常量
字符常量用带单引号的ASCII字符表示,它所代表的数值就是该字符的ASCII码。 例如:‘A’就是41H,‘1’就是31H等。
3.符号常量
为编程方便,程序员在编程时可将一个标识符定义为一个符号常量,它具有一个设定 的数值而可被引用。
例如符号常量定义语句ALLONE=llllllllB将ALLONE定义为11111111B。在程序中即可引用如MOV AL,ALLONE,此条指令和MOV AL,llllllllB是等效的。
(二)标识符
标识符是程序员在编程时建立的有特定意义的字符序列,标识符可用作符号常量、名
字、变量和标号等。
对于标识符,有以下规定:
(1)组成标识符的字符有:英文大写字母A、B:…、Z;英文小写字母a、b、…、z ,数字0、1、2、…、9;字符?、@、—。
(2)标识符的有效长度为不超过31个字符,若超过就忽略其超过部分。
(3)除数字以外,所有规定的字符都可作为标识符的第一个字符。
(4)问号?不能单独作为一个标识符。
(5)不能把保留字用作标识符。保留字包括:指令和命令的助记符,如MOV、SEGMENT等;寄存器名,如AX等;语句中的规定用词,如PTR、LENGTH等。
(三)表达式
表达式由操作数和运算苻组成。操作数可为常量、名字、变量和标号等。运算符包括
算术运算符、逻辑运算符等很多种,如表4-1所示。表中各运算符是按运算优先级由高向 低逐行排列的。
例如指令性语句:MOV CX,COUNT-1,其中COUNT-1为表达式,设符号常量COUNT已用COUNT=10定义,则汇编时将表达式汇编成9(注:COUNT-l=10-1=9),即将该指令按
MOV CX,9汇编成机器码供程序运行时由CPU执行。
又如指令性语句:MOV AL,COUNT LT 20,其中COUWT LT 20为表达式,其中 LT为比较运算符“小于”,设COUNT已定义为10,由于10<20,即表达式是成立的,
亦即表达式的规定条件是“真”(TRUE)。汇编时将该表达式汇编成0FFH即“真”(即全“1”),则将该指令按MOV AL,0FFH汇编成机器码供程序运行,若指令性语句为
MOV AL,COUNT GT 20,其中GT为表示“大于”的比较运算符,则表达式是不成立的,即是“假”(False),汇编时就将该表达式汇编成00H。
表达式中的运算操作按运算符的优先级别先高后低地依次进行,对于相同优先级则按 从左到右的次序进行运算操作。
三、指示性语句
指示性语句的格式如下:
[名字/变量] 命令 [参数] [;注释]
(一)程序开始和结束语句
程序开始和程序结束语句的命令有NAME、TITLE和END等。
1.应用命令NAME的程序命名语句命令NAME用来给程序模块命名,其格式为
NAME 名字
其中名字是程序员按标识符规定所取的程序模块名,汇编后它就成为该程序模块的名 字了。
2.应用命令TITLE的标题命名语句
若所用汇编语言没有NAME命令,则可用命令TITLE,其格式为
TITLE 名字
则其中由程序员所取的名字在程序的每一页作为标题打印出来。
标题的名字最多可有60个字符。若程序没有使用NAME命令,就用标题名字中的前 面6个字符作为模块名。注意:程序开始时不用NAME和TITLE命令的语句是允许的,此时可直接由段定义语句开始编写源程序。
3.应用命令END的程序结束语句
程序结束语句的格式为
END [标号]
其中标号是程序中第1句指令性语句(或第1条指令)的标号。当程序由多个模块组 成时,只需在主程序中的结束语句中写出标号,其它子程序模块的结束语句只要写出命令 END即可。
(二)段定义语句
分段结构是8086/8088的特点,程序和存储器都是按段来组织的。段定义语句用来定义一个段,命令有SEGMENT、ENDS、ASSUME和ORG等。
1.应用命令SEGMENT和ENDS的段定义语句
其格式为
段名 SEGMENT [参数]
┅
段名 ENDS
其中段名为程序员编程时按标识符规定为该段所取的名字,在汇编和连接时系统将给 该段名的段分配一个具体的段基址。
命令SEGMENT和ENDS必须成对使用,它们前面的段名必须是一致的,SEGMENT语句和ENDS语句之间就是该段的内容。
例如:DATA SEGMENT
┅
DATA ENDS
CODE SEGMENT
┅
CODE ENDS .
此例共有两段,段名分别为DATA和CODE,且显然是特意这样取名以表示第一段是数据段,而第二段是代码段。
2.SEGMENT语句中的参数部分
SEGMENT语句中的参数共有三项,语句的格式为
段名 SEGMENT [定位类型] [组合类型] [‘类别’]
这三个参数用来设定该段在内存中的位置,且都是任选项。
(1)定位类型(align-type)。定位类型用来指定该段段地址的边界条件、定位类型 有以下四种:
1) BYTE 该段可从任何地址开始,即段地址 =
X X X X, X X X X, X X X X, X X X X, X X X X B,
其中X表示任意值,即1或0;
2) WORD 该段必须从字的边界开始,即段地址 =
X X X X, X X X X, X X X X, X X X X, X X X O B
3) PARA 该段必须从节的边界开始,即段地址 =
X X X X, X X X X, X X X X, X X X X ,0 0 0 0 B
4) PAGE 该段必须从页的边界开始,即段地址 =
X X X X, X X X X, X X X X, 0 0 0 0, 0 0 0 0 B。
注意:当定位类型缺省(即不写)时,隐含值为PARA。
(2)组合类型(combine-type)。在汇编和连接时,当该段与其他段组合在一起时,组合类型用来设定该段与其它段的连接关系,组合类型有以下六种:
1)PUBLIC 该段连接时将与其他同名段依次连接起来,其连接次序由连接程序确 定;
2)COMMON 该段连接时将与其他同名段有相同的段基址.即共享相同的存储空 间.亦即各段会产生覆盖,但可节省内存容量;
3)AT exp 使段基值等于按表达式exp计算所得的16位数。必须指出,对于代码段 不能用ATexp来设定段基值;
4)STACK 仅用于堆栈段,使同名段都从同一段基址开始:
5)MEMORY 指定该段在同名段的最后,即该段在同名段中位于最高的地址空间,若连接时有几个组合类型为MEMORY的段,则只有最前面(最先遇到)的段按组合类型 MEMORY处理,其他段均按组合类型PUBLIC处理;
6)NONE表示本段与其它段逻辑上不发生关系,各段都有自己的段基址。 注意:当组合类型缺省(即不写)时,隐含值为NONE。
(3)‘类别’(‘dass’)。类别必须用单引号括起来。在定位时,连接程序将各程序模块中具有相同类别的逻辑段集中在一起,形成一个统一的物理段。
3.应用命令ORG的偏移地址定位语句
程序中有时需要指定某一语句所在内存单元在段内的偏移地址,这可用ORG命令的 语句来实现,语句的格式为
ORG exp
其中表达式exp,可计算得出16位地址。此时ORG语句的下一个语句所在内存单元 在段内的偏移地址就被指定为按表达式计算得出的16位地址。
例如:
ORG 1000H
MOV AL, BL
本例示明指令MOV AL,BL所在内存单元在本段内的偏移地址为1000H。
4.用命令ASSUME示明段寄存器内容的语句
一个程序通常由很多段组成,对于某一代码段,它所用到的数据段、附加段和堆栈段 只是程序中的某几个有关段,因此在代码段开始时必须用ASSUME命令语句示明该代码 段所用到的段,以便进行汇编。
ASSUME命令语句紧跟在SEGMENT命令语句之后,其格式为
ASSUME CS:段名1,DS:段名2,ES:段名3,SS:段名4
其中段名1、2、3和4分别为该代码段所用到的作为代码段、数据段、附加段和堆栈段的段的段名,亦即将这些段的段基值1、2、3和4作为段寄存器CS、DS、ES S5的内容。
注意:在ASSUME语句中示明的段寄存器和相应的段名是该代码段中实际用到的, 对于该代码段中未用到的段寄存器及相应的段名是不需示明的。
例如:CODE SEGMENT
ASSUME CS:CODE,DS:DATA
CODE ENDS
本例表明在段名为CODE的代码段中,所用到的段寄存器为CS和DS,它们的内容分别是CODE段和DATA段的段基值。
(三)过程定义语句
过程是程序的一部分,即子程序。过程可用程序中的CALL指令调用。当过程中的指令执行完后,用RET指令返回调用它的程序。
应用命令PROC和ENDP的过程定义语句的格式为:
过程名 PROC 类型
┅
RET
过程名 ENDP
过程名是程序员编程时按标识符规定取定的。类型表明该过程是供段内调用,还是供 段间调用,对于前者用NEAR表示,而后者则用FAR表示,当类型项缺省(即不写明)时,隐含值为NEAR。
PROC和ENDP是成对使用的,两语句之间就是该过程的内容(程序),且用RET指令结尾以返回调用它的程序。
成对使用的命令PROC和ENDP的前面必须均写明该过程名。
(四)数据定义语句
数据定义语句用来为数据分配存储单元,例如在内存中设置原始数据以及为存放结果 数据而保留内存单元等。数据段、附加段和堆栈段都是存放数据的,其中所用的语句主要 是数据定义语句。数据定义的命令有DB、DW、DD、DQ和DT等,它们分别用来定义不同类型(长度)的数据。
数据定义语句的格式为
[变量] 命令 参数1,参数2,… ,[;注释]
其中变量是由程序员在编程时按标识符规定取定的,如ARRAY、BUFFER、SUM等,一般都是按照数据的功用取名的。
其中命令的表示符号(助记符)及功能为
DB 定义长度为1字节(8位)的数据(字节数据)。
DW 定义长度为1个Z字(16位)的数据(字数据)。
DD 定义长度为2个字(32位)的数据(双字数据)。
DQ定义长度为8字节(64位)的数据(8字节数据)。
DT 定义长度为10字节(80位)的数据(10字节数据)。
其中参数就是相应内存单元中的数据,它可以是常数(可用各种规定的数制表示)、 字符常数(用单引号括起来的ASCII字符)或符号常数,当它是保留以备存入有关数据 时就以问号(?)表示。参数可以有多个,相互间要用逗号(,)隔开,若连续多个数据是
重复的,就可应用复制符DUP以简化书写,DUP的用法为
复制次数 DUP(数据)
其中数据可以不只一个,且数据还可有复制部分。
例1 段名为DATA的段由以下语句组成
DATA SEGMENT
DATA1 DB 20H
ARRAY DB 12H,12,‘A’
SUM DB ?
DATA ENDS
设本段的段基值为2000H,则相应内存分配为内存
地址 内容
段基值:偏移地址
2000H: 0000H 20H
0001H 12H
0002H 0CH
0003H 41H
0004H ?
其中第1个数据20H在该段的起点,故相应内存单元的偏移地址为0000H,后面数 据所在内存单元的偏移地址依次类推。
其中所有内存单元的段基值都是相同的(=2000H),其中内容的数据都是以16进制 表示的(注:由汇编程序在汇编时进行转换)。
其中“?”表示保留,实际该内存单元的内容为随机数,但对本程序来说,目前是无效的。
例2 DATA2 DB 2DUP(12H,34H,56H)
此时内存分配为
注:未列出具体地址
例3 DATA3 DB ‘ABCD’
其中参数部分‘ABCD’是‘A’,‘B’,‘C’,‘D’的简写。
例4 DATA4 DW 1234H,5678H,9AH,?
其内存分配为
用DW定义的是字数据,每个数据分配2个内存单元,如数据9AH实则上是 009AH。
例5 DATA5 DW ‘AB’,‘CD’
其内存分配为
注意:不能写为DW ‘CDAB’,因为汇编语言语法规定除用DB定义的字符串常量 外,单引号中ASCII字符的个数不得超过2个,若只有1个,例DW‘C’,就相当于DW 0043H。
例6 DATA6 DD 12345678H,‘AB’
其内存分配为
例7 STAK DB 100 DUP(?) ;保留100个字节内存单元作为堆栈区
(五)符号定义语句
符号定义语句的命令有EQU、= 和PURGE
1.应用命令EQU和PURGE的符号定义语句
应用EQU命令的语句的格式为
名字 EQU exp
其中名字是程序员取定的,表达式exp可以计算得出一个具体的数值。这实际上就是给名字赋值,在程序中就可引用这个名字来表示表达式的实际计算值。
若需对已赋值的名字撤消原赋值并赋以新值。则需先用PURGE命令语句撤消原赋值,再用EQU命令语句赋新值。PURGE命令语句的格式为
PURGE 名字
其中名字可不只一个,即可同时撤消几个已赋值。
例如:COUNT EQU 20 ;给COUNT赋值为20
┅
MOV AL,COUNT ;即MOV AL,20
┅
PURGE COUNT ;撤消原赋值
COUNT EQU 10 ;给COUNT赋新值为10
┅
MOV BL,COUNT ;即MOV BL,10
┅
2.应用命令=的符号常量定义语句,其格式为
名字 = exp
命令 = 的功能与EQU类似,唯一的差别是命令;可随时对名字(符号常量)赋新值,而不必使用PURGE命令。
如上例,可写为
COUNT = 20
┅
MOV AL,COUNT
┅
COUNT = 10
┅
MOV BL,COUNT
┅
(六)名字和变量
1.名字
前面已讨论过的名字有文件名、标题名、段名、过程名和符号常量等,它们都是程序 员编程时按标识符规定来命名的,其中有些名字可在编程时引用,以方便编程。
(1) 段名。段名是在源程序的段定义语句中命名取定的,如段定义语句
DATA SEGMENT, 段名为 DATA。
源程序在进行汇编连接时,系统分配给该段一个段基值,设为2000H。这时段名
DATA就可作为段基值2000H被引用。
例如:给段寄存器赋值的指令序列为
MOV AX,DATA ;相当于MOV AX,2000H
MOV DS,AX ;将段基值赋给段寄存器
(2) 过程名。过程名是在源程序的过程定义语句中命名的。如过程定义语句
SORT PROC NEAR,过程名为SORT。
汇编连接源程序时,系统分配给过程一个地址,即该过程第一条指令所在内存单元的 地址,亦即该过程的入口地址,这也就是调用该过程的CAl厶指令中的目的地址。过程名在汇编语言程序中可作为调用指令的目的地址使用,例如指令CALL SORT,其中 SORT就表示过程名为SORT的过程的入口地址,执行该指令就是转移到过程SORT,运行。
(3)符号常量。符号常量是在源程序的符号常量定义浯句中命名取定的,如符号常量定义语句COUNT EQU 20将数值20赋给COUNT。COUNT就可在指令中作为常量20
被引用,如MOV AL,COUNT就相当于MOV AL,20。
例1 部分程序内容为
DATA SEGMENT
ARRAY DB 10H,24H,5AH,0C7H,98H,‘ABCDE’
COUNT EQU $-ARRAY
MAX DB ?
DATA ENDS
其中第3语句行为给符号常量COUNT赋值的语句,句中表达式为$-ARRAY,其计算值就是赋给COUNT的常量数值。下面先讨论表达式中$和ARRAY的含义及具体值,然后由表达式计算出赋给COUNT的具体数值。如本小节(四)所讨论,变量ARRAY所在语句中的第一个数据10H所在内存单元的偏移地址为0000H,也就是该数组的起始地址,后续数据依次存在后续偏移地址的内存单元中,最后一个数据‘E’所在内存单元的偏移地址为0009H。变量ARRAY所在语句的起始偏移地址为0000H,这就是变量 ARRAY的偏移地址属性(注:变量的属性将在下面详细讨论)。由于ARRAY语句的末 地址为0009H,故下一语句行(COUNT语句行)所在的偏移地址就是000AH,此语句中的 $ 就是该行(当前行)的偏移地址,即000AH。故表达式可计算得出为
$-ARRAY=000AH-0000H=000AH=10
因而赋给符号常量COUNT的值为10,COUNT的英文含义为计数,它实际上表示的是ARRAY数组的数据元素个数,即10十字节数据。在程序中用MOV CX,COUNT 指令来设置计数器CX的初值,然后就可用对CX进行减一计数的方法来控制对10个数据的处理。
例2 部分程序内容为
STACK SEGMENT
STAK DB 100 DUP(?) ;保留100个内存单元(字节)作堆栈区
TOP EQU $—STAK ;给TOP赋值为100
STACK ENDS
CODE SEGMENT
┅
MOV AX,STACK ;将段基值STACK赋给段寄存器SS
MOV SS,AX
MOV SP,TOP ;设置堆栈指针
此时,内存中堆栈段的分配如下:
2.变量
(1)变量的定义和属性。如前所述,变量是数据定义语句中的一项,它是由程序员在 编程时按照标识符规定取定的。
当在数据定义语句的第一项对变量命名后,该变量就是已定义了的,已定义的变量具 有下列五种属性:
1)段属性
表示格式: SEG 变量
它表示变量所在段的段基值。
2)偏移地址属性
表示格式: OFFSET 变量
它表示变量所在处的偏移地址。
3)类型属性
表示格式: TYPE 变量
它表示变量所在内存数据的类型。数据类型有字节、字、双字、8字节和10字节,是在该语句中用命令DB、DW、DD、DQ和DT予以定义的。对于不同的数据类型,变量的类型属性具有如下不同的值:
字节数据时, TYPE=1
宇数据时, TYPE=2
双字数据时, TYPE=4
8字节数据时, TYPE=8
10字节数据时,TYPE=10
4)长度属性
表示格式: LENGTH 变量
它表示变量所在数组的数据元素个数。
需注意,只有当数据用复制符 DUP 定义时,LENGTH才等于数组的元素个数,否则
LENGTH就等于1。
5)规模属性 、
表示格式:SIZE变量
它表示变量所在数组的字节总数,且
SIZE:LENGTH*TYPE。
同以上LENGTH的情况,只有当数据用复制符DUP定义时,LENGTH才等于数组的元素个数,否则LENGTH就等于1。
这些属性,程序员编程时均可引用,从而方便编程。
例如:部分程序内容为
DATA SEGMENT
BUFl DB NI,N2,N3,…,N10 ;N1~N10为10个字节数据
BUF2 DB 10 DUP(0)
BUF3 DW 10 DUP(?)
DATA ENDS
设该段的段基值为2000H,则
SEG BUF1=2000H
OFFSET BUF1=0000H
TYPE BUF1=1
LENGTH BUF1=1
SIZEBUF1=1
SEGBUF2=2000H
OFFSETBUF2=000AH
TYPEBUF2=1
LENGTH BUF2=10
SIZEBUF2=10
SEGBUF3=2000H
OFFSETBUF3=0014H
TYPE BUF3=2
LENGTHBUF3=10
SIZE BUF3=20
这些属性在程序中的应用举例
MOV AX,SEG BUFl ;设置段寄存器DS
MOV DS,AX
MOV SI,OFFSET BUFl ;设置地址指针SI
MOV CX,LENGTH BUF2 ;设置计数器CX
MOV BL,SIZEBUF3 ;设置计数器BL
MOV AL,BUF1 ;从内存取数据到寄存器AL。此指令是指令
;MOV AL,OFFSET BUFl]的简便写法,是汇编程序能接受的。
;本指令的功能是将数据N1送到寄存器AL,
MOV AH,BUFl+2 ;这是MOVAH,[OFFSETBUFl+2]的简便写法,指令的功能是将
;数据N3送到寄存器AH
(2)属性运算符。如前所述,定义了的变量具有一定的属性。对变量属性的引用可方 便程序员的编程工作。但变量的类型属性有时会限制它的应用。
设数据定义语句为:
BUFW DW 1234H,5678H
其中变量BUFW的类型属性为字,编程时可很方便地用指令:
MOV AX,BUFW
将字数据1234H传送到寄存器AX。但若要传送字节数据就有问题了,因为指令 MOV AL,BUFW 是非法的,所以非法是由于指令中的两个操作数AL(字节数据)和BUFW(字数据)的类型不同,这样的指令是不能汇编和执行的。为了解决这个问题,汇编语言提供了属性运算符PTR、THIS和LABEL:
1)类型(重新)指定运算符PTR
格式:类型PTR exp
其中类型可以是BYTE、WORD、DWORD、NEAR和FAR(注:前三个是变量的类型属性,后两个是标号的类型属性);exp是表达式,是存储器操作段,当为变量重新指定类型时exp就是变量名。
运算符PIR的作用是仍按后面的表达式去寻址,不管它原来有无类型或是那一种类 型,PTR定义后,就按PIR前面类型项指定的类型看待,实际上,PTR是给后面的存储器操作数赋予新的前面的数据类型(注:对于标号则为地址类型)。运算符PTR的应用如下:
a.重新指定变量类型
BUFW DW 1234H,5678H
下列指令均为合法的
MOV AX,BUFW ;AX ← 1234H
MOV AL,BYTE PTRBUFW ;AL ← 34H
b.指定内存操作数的类型,下列指令
INC [BX] 非法的,因为基址寻址的内存操作数的类型未示明,无法进行汇编和执行。
用PTR指定类型后,指令就是合法的了,如
INC BYTE PTR [BX]
INC WORD PTR [BX]
c.用EQU和PTR定义一个新的变量
BUFW DW 1234H,5678H
BUFB EQU BYTE PTR BUFW
这使新变量BUFB具有和变量BUFW相同的段属性、偏移地址属性,但两者类型不
同,BUFW类型属性为字,而BUFB类型属性为字节。 此时,下列指令就是合法的。
MOV AX,BUFW ;AX ← 1234H
MOV AL,BUFB ;AL ← 34H
2)属性指定运算符THIS ,格式:
THIS 类型
类似于上节PTR运算符的应用。THIS和EQU一起用来定义一个新变量,它与原变 量具有相同的段属性和偏移地址属性,但类型属性不同。
例如:BUFB EQU THIS BYTE
BUFW DW 1234H,5678H
这样,BUFB和BUFW具有相同的段属性和偏移地址属性,但BUFB的类型属性是字节,此时下列指令都是合法的。
MOV AX,BUFW ;AX ← 1234H
MOV AL,BUFB ;AL ← 34H
注意,BUFB语句和BUFW语句必须是紧邻的,且BUFB语句在BLN语句的前
面。
3)命令LABEL
格式: 变量/标号 LABEL 类型
命令LABEL用来定义其语句中的变量(或标号)的类型属性为语句中设定的类型, 此时变量(或标号)的段属性和偏移地址属性是由该语句的位置确定的。
例如:BUFB LABEL BYTE
BUFW DW 1234H,5678H
则下列指令是合法的
MOV AX,BUFW ;AX ← 1234H
MOV AL,BUFB ;AL ← 34H
注意,BUFB语句和BUFW语句必须是紧邻的,且BUFB语句在BUFW语句的前面。
四、指令性语句
指令性语句由指令组成。指令性语句是构成代码段的基础。指令性语句只是在代码段 中才有,在数据段、附加段和堆栈段中是没有指令性语句的。程序运行时,CPU执行指令性浯句中的指令,而指示性语句在程序运行时,是不由CPU执行的,指示性语句是用来指示汇编程序进行汇编操作的。
指令性语句的格式为
[标号] 操作码 [操作数] [;注释]
其中操作码和操作数就是指令,本书第三章已讨论了8086/8088指令系统,此处不再 赘述。
本节将对标号和操作数进行讨论。
(一)标号
标号是程序员编程时按标识符规定取定的,并常常具有它在程序中的作用的含义,如
NEXT、AGAIN等,并且标号一定要用冒号(:)结尾。
在指令性语句中写上标号后,就定义了该标号,定义了的标号具有下列三种属性。
1 段属性
表示格式:SEG标号
它表示标号所在段的段基址。
2.偏移地址属性
表示格式:OFFSET标号
它表示标号所在位置的偏移地址,即该语句的指令的第一字节所在内存单元的偏移地 址。
3.类型属性
表示格式:TYPE标号
标号通常用作转移指令的目的操作数,即转移去的目的地址。我们知道,转移有 NEAR(近转移,即段内转移)和FAR(远转移,即段间转移)两种,NEAR和FAR是转移的两种类型,因而也就是标号的类型。类型为NEAR的标号供段内转移用,而类型为FAR的标号则供段间转移用。
标号的类型属性和标号的类型有关:
NEAR时,TYPE = -1
FAR时,TYPE = -2
其中的-1和-2投有真正的物理意义,只是以这样的具体数值表示而已(注:由于要和变量的类型属性清楚地区别,所以用的是负值)。
指令性语句中的标号,其类型一般都是NEAR。为了适应段间调用的需要,要把它的类型改换成FAR。与前面变量改变类型相类似,标号类型的改变可应用运算符PTR、THIS和LABEL,可以是重新指定类型,也可以定义一个新标号。
设程序中标号为METER,其类型为NEAR。若定义一个新变量KILOMT,其类型为FAR,而KILOMT的段属性和偏移地址属性则是和METER的相同。这样,段内转移时用METER作为目的操作数,而段间转移时则用KILOMT作为目的操作数,两个标号表示的目的地址是同一个,即均转移到程序中标号为METER处。具体方法如下。
(1)用PTR重新指定类型:
段内转移用指令
JMP METER
段间转移用指令
JMP FAR PTR METER
(2)用EQU和PTR定义新标号KILOMT
METER:┅
KILOMT EQU FAR PTR METER
(3)用EQU和THIS定义新标号KILOMT
KILOMT EQU THIS FAR
METER:┅
(4)用LABEL定义新标号KILOMT
KILOMT LABEL FAR
METER: ┅
这四种方法和前面变量改变类型的方法是一样的,这里不赘述。
(二)操作数
指令中的操作数可按寻址方式表示,寻址方式在前面第三章中已讨论,此处不再赘述。 操作数也可以用段名、符号常量、变量、属性、过程名和标号来表示,如下例所示
MOV AX,DATA ;DATA是段名,立即寻址方式
MOV CX,COUNT ;COUNT是符号常量,立即寻址方式
MOV BL,BUFFER ;BUFFER是变量,直接寻址方式
MOV SI,OFFSET ARRAY ;OFFSETARRAY是属性,立即寻址方式
CALL SBRT1 ;SBRT1是过程名。直接寻址方式
JMP DONE ;DONE是标号,直接寻址方式
LOOP AGAIN ;AGAIN是标号,直接寻址方式
五、宏指令
程序员用汇编语言编程时,对于程序中多次重复使用的指令序列(即很小的程序段), 可定义一条宏指令,编写程序时就用这条宏指令代替该指令序列,从而简化书写工作:
(一)宏定义、宏名字、宏调用和宏展开
宏定义就是定义宏指令,宏定义的命令是MACRO和ENDM。宏定义的格式为
宏名字 MACRO [形式参数]
┅
ENDM
其中宏名字是宏指令的名字,是程序员按标识符规定取定的。命令MACR0和 ENDM之间的指令序列就是该宏指令的内容,称为宏体。带方括号的形式参数是任选项,当无形式参数时就无该项,当有多个形式叁数时相互间由逗号隔开。形式参数亦称哑参数、哑元或变元,它是宏体中有关指令的操作码、操作数或它们的一部分。形式参数是没有物理童义的,只有用实参数代替形式参数后,相应的指令才有实际意义。
在程序中应用宏指令称为宏调用,宏调用的格式是:
宏名字 [实参数]
其中实参数是与宏定义中的形式参数一一对应的。
汇编时,汇编程序用已由实参数取代形式参数的宏体取代程序中的宏指令,称为宏展 开,此时程序中已全部是可执行的指令序列了。
例1 无形式参数的宏指令
宏定义 SAVEREG MACRO
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
ENDM
宏指令名为SAVEREG,功能是有关寄存器内容进栈保护现场。
宏调用
程序
┅
SAVEREG
┅
SAVEREG
宏展开
程序
┅
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
┅
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
汇编程序再将此宏展开后的程序转换成机器码生成目标程序,
例2 带有形式参数的宏指令
宏定义MULTIPLY MACRO OPRl,ORR2,RESULT
PUSH AX
MOV AL,OPRl
1MUL OPR2
MOV RESULT,AX
POP AX
ENDM
其中有三个形式参数OPRl、OPR2和RESULT,它们都是宏体指令中的操作数。
宏调用程序
MULTIPLY CL,VAR,XYZ[BX] ;实参数CL是寄存器,VAR是变量,
XYZ[BX]是基址寻址的内存操作数,
MULTIPLY 240,BL,SAVE ;实参数240是立即数,BL是寄存器,
SAVE是变量
宏展开程序
PUSH AX
MOV AL,CL
IMUL VAR
MOV XYZ[BX],AX
POP AX
┅
PUSH AX
MOV AL,240
IMUL BL
MOV SAVE,AX
POP AX
┅
汇编程序将此宏展开后的程序转换成机器码生成目标程序。
(二)宏指令与子程序的差别
如上所述,宏指令是将一段程序(指令序列)用一条宏指令来代替,以简化书写源程 序。子程序(过程)也有类似的功能,但两者是有差别的,具体如下:
(1) 宏指令简化了源程序的书写。但在汇编时,汇编程序对宏指令的汇编处理是将宏指
令的宏体(即程序段)原原本本地插入到宏指令调用处,然后转换成机器码生成目标程 序。因此,宏指令虽简化了源程序,但并没有简化目标程序,有多少次宏调用,在目标程 序中就有同样多次数的目标代码插入。所以宏指令不节省目标程序需占用的内存单元。
子程序(过程)在执行时是由CPU用调用(CALL)来处理的。若在一个源程序中多次调用同一个子程序,则在目标程序中,主程序中只有调用(CALL)指令的目标代码,CALL指令的目标代码只有几个字节,该目标代码出现的次数就是调用次数。而子程序的目标代码在整个目标程序中只出现一次,所以相应地其目标程序就占用较少的内存单元,即可节省内存单元。
(2)采用子程序方式时,每调用一次就需执行一次CALL和RET指令,而宏指令方式时,并无此两条指令。因此,使用宏指令时的程序执行时间比子程序时的程序执行时间要短一,即宏指令时程序执行速度快。
由上可知,宏指令和子程序各有特点,宏指令执行速度快而子程序占用内存少。一 般,对于程序段较长的情况,采用子程序可节省很多内存而对执行速度影响不大;对于程 序段较短的情况,采用宏指令可加快速度而对增加占用内存容量影响不大,尤其对于程序 段较短而形式参数较多的情况,宏指令就更能显示其突出的优点了。
一、汇编语言程序的格式
(一) 基本概念
程序是为实现某一特定目的(例如对数据进行某种处理)而编写的一组指令有序集 合。汇编语言程序就是用汇编语言编写的源程序。汇编语言是一种面向机器的语言,它是与计算机硬件密切关连的,因而熟悉计算机硬件是汇编语言程序员必须具备的条件。与用高级语言编写的程序相比较,汇编语言程序具有更高的效率,它的程序执行时间短且占用内存少,这在计算机实时控制和实时处理中是十分重要的,因而在实时领域得到广泛应用。用汇编语言编写的源程序,必须由汇编程序(一种系统软件)进行汇编,将它转换成用机器语言表示的目标程序后,才能由CPU识别执行。因此编制程序时必须遵循规定的格式和语法,这是本章讨论的主要内容之一。对于不同型号的CPU和不同版本的汇编程序,其汇编语言是不同的。对于同一系列的CPU,则是向上兼容的。本书是针对8086/8088CPU进行讨论的。
(二)汇编语言源程序的特点和格式
下面列举了一个汇编语言程序,其功能是对10个字节数据a1 ~ a10求和。
DATA SEGMENT AT 2000H
ARRAY DB A1,A2,A3,…,A10
COUNT EQU $-ARRAY
SUM DW ?
DATA ENDS
STACK SEGMENT PARA STACK ‘STACK’
STAK DB 100 DUP(?)
TOP EQU LENGTH STAK
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START:MOV AX,DATA
MOV DS,AX
MOV AX,0
MOV DI,OFFSET SUM
MOV BX,OFFSET ARRAY
MOV CX,COUNT
LOP: ADD AL,[BX]
ADC AH,0
INC BX
LOOP LOP
MOV [DI],AX
MOV AH,4CH
INT 21H
CODE ENDS
END START
此例明显地示出了汇编语言程序的两个组成特点:分段结构和浯句行。
1.分段结构
在第二章中,我们已讨论了8086/8088的程序分段,知道程序最多可由4段组成,并 分别由段寄存器CS、DS、ES和SS的内容作为段基值,每段所占内存容量最大可达64KB。
上例程序共有3段,它们分别是数据段(段名DATA)、堆栈段(段名STACK)和代码段(段名CODE),各段由命令SEGMENT开始,并由命令ENDS结束。
2.语句行
上例程序共有26行,每行1个语句,即共有26个语句行。汇编语言程序的语句有两类:指令性语句和指示性语句。
(1)指令性语句。指令性语句是主要由指令构成的语句,其格式为
[标号:] 操作码 [操作数] [;操作数] [;注释];
其中操作码和操作数是用助记符表示的指令的两个部分,8086/8088的指令系统已在 第三章中讨论过,此处不再赘述。
其中带方括号的三项(标号操作数和注释)是任意选用的项,即根据具体编程需要该 项可有或没有,当然在实际语句中该项的方括号是不写出来的。标号具有该语句指令所在 内存地址的属性,通常在转移指令中用作目的地址。注意,标号必须用冒号“:”结尾,这是语法的规定。用分号“;”开始的注释用来说明该语句在程序中的作用,以方便程序的阅读和修改,这项也是任选的。
如上例的第18语句行
LOP:ADD AL,[BX]
其中指令是ADD AL,[BX],标号是LOP。LOP在第21语句行指令LOOP LOP中是转移的目的地址。又此语句中未用注释项。
(2)指示性语句。指示性语句是主要由命令(亦称伪指令)构成的语句,是用来指示汇编程序进行汇编操作的,其格式为:
[名字/变量] 命令 [参数] [;注释]
其中命令指示汇编程序进行某种汇编操作,参数是有关的数据,带方括号的项是任选 的。
如上例的第1语句行
DATA SEGMENT AT 2000H
其中命令SEGMENT指出这是一个段的开始,参数AT 2000H指定该段的段基值为 2000H,而DATA则是该段的名字。
又如上例的第5语句行
DATA ENDS
则表明了段名为DATA的段的结束。指示性语句的应用使程序员编程时不需进行很多计算,既方便又简化了编程工作。
二、常量、标识符和表达式
常量、标识符和表达式在汇编语言源程序中经常用到,本节对它们分别作一介绍。
(一)常量
常量是具有一定数值的量,汇编语言程序中的常量有:数字常量、字符常量和符号常量:
1.数字常量
数字常量可用二进制数、八进制数、十进制数或十六进制数表示。所用的数制要用后 缀示明:二进制为B、八进制为Q、十进制为D、十六进制为H。对于十进制数可以用后缀D,也可以不用后缀。对于十六进制数,若最高位的数为A-F,须在它的前面加上数字0,以表明是数值数据。
例如:10100110B,246Q,166D,166,6AH,0A6H。
2.字符常量
字符常量用带单引号的ASCII字符表示,它所代表的数值就是该字符的ASCII码。 例如:‘A’就是41H,‘1’就是31H等。
3.符号常量
为编程方便,程序员在编程时可将一个标识符定义为一个符号常量,它具有一个设定 的数值而可被引用。
例如符号常量定义语句ALLONE=llllllllB将ALLONE定义为11111111B。在程序中即可引用如MOV AL,ALLONE,此条指令和MOV AL,llllllllB是等效的。
(二)标识符
标识符是程序员在编程时建立的有特定意义的字符序列,标识符可用作符号常量、名
字、变量和标号等。
对于标识符,有以下规定:
(1)组成标识符的字符有:英文大写字母A、B:…、Z;英文小写字母a、b、…、z ,数字0、1、2、…、9;字符?、@、—。
(2)标识符的有效长度为不超过31个字符,若超过就忽略其超过部分。
(3)除数字以外,所有规定的字符都可作为标识符的第一个字符。
(4)问号?不能单独作为一个标识符。
(5)不能把保留字用作标识符。保留字包括:指令和命令的助记符,如MOV、SEGMENT等;寄存器名,如AX等;语句中的规定用词,如PTR、LENGTH等。
(三)表达式
表达式由操作数和运算苻组成。操作数可为常量、名字、变量和标号等。运算符包括
算术运算符、逻辑运算符等很多种,如表4-1所示。表中各运算符是按运算优先级由高向 低逐行排列的。
例如指令性语句:MOV CX,COUNT-1,其中COUNT-1为表达式,设符号常量COUNT已用COUNT=10定义,则汇编时将表达式汇编成9(注:COUNT-l=10-1=9),即将该指令按
MOV CX,9汇编成机器码供程序运行时由CPU执行。
又如指令性语句:MOV AL,COUNT LT 20,其中COUWT LT 20为表达式,其中 LT为比较运算符“小于”,设COUNT已定义为10,由于10<20,即表达式是成立的,
亦即表达式的规定条件是“真”(TRUE)。汇编时将该表达式汇编成0FFH即“真”(即全“1”),则将该指令按MOV AL,0FFH汇编成机器码供程序运行,若指令性语句为
MOV AL,COUNT GT 20,其中GT为表示“大于”的比较运算符,则表达式是不成立的,即是“假”(False),汇编时就将该表达式汇编成00H。
表达式中的运算操作按运算符的优先级别先高后低地依次进行,对于相同优先级则按 从左到右的次序进行运算操作。
三、指示性语句
指示性语句的格式如下:
[名字/变量] 命令 [参数] [;注释]
(一)程序开始和结束语句
程序开始和程序结束语句的命令有NAME、TITLE和END等。
1.应用命令NAME的程序命名语句命令NAME用来给程序模块命名,其格式为
NAME 名字
其中名字是程序员按标识符规定所取的程序模块名,汇编后它就成为该程序模块的名 字了。
2.应用命令TITLE的标题命名语句
若所用汇编语言没有NAME命令,则可用命令TITLE,其格式为
TITLE 名字
则其中由程序员所取的名字在程序的每一页作为标题打印出来。
标题的名字最多可有60个字符。若程序没有使用NAME命令,就用标题名字中的前 面6个字符作为模块名。注意:程序开始时不用NAME和TITLE命令的语句是允许的,此时可直接由段定义语句开始编写源程序。
3.应用命令END的程序结束语句
程序结束语句的格式为
END [标号]
其中标号是程序中第1句指令性语句(或第1条指令)的标号。当程序由多个模块组 成时,只需在主程序中的结束语句中写出标号,其它子程序模块的结束语句只要写出命令 END即可。
(二)段定义语句
分段结构是8086/8088的特点,程序和存储器都是按段来组织的。段定义语句用来定义一个段,命令有SEGMENT、ENDS、ASSUME和ORG等。
1.应用命令SEGMENT和ENDS的段定义语句
其格式为
段名 SEGMENT [参数]
┅
段名 ENDS
其中段名为程序员编程时按标识符规定为该段所取的名字,在汇编和连接时系统将给 该段名的段分配一个具体的段基址。
命令SEGMENT和ENDS必须成对使用,它们前面的段名必须是一致的,SEGMENT语句和ENDS语句之间就是该段的内容。
例如:DATA SEGMENT
┅
DATA ENDS
CODE SEGMENT
┅
CODE ENDS .
此例共有两段,段名分别为DATA和CODE,且显然是特意这样取名以表示第一段是数据段,而第二段是代码段。
2.SEGMENT语句中的参数部分
SEGMENT语句中的参数共有三项,语句的格式为
段名 SEGMENT [定位类型] [组合类型] [‘类别’]
这三个参数用来设定该段在内存中的位置,且都是任选项。
(1)定位类型(align-type)。定位类型用来指定该段段地址的边界条件、定位类型 有以下四种:
1) BYTE 该段可从任何地址开始,即段地址 =
X X X X, X X X X, X X X X, X X X X, X X X X B,
其中X表示任意值,即1或0;
2) WORD 该段必须从字的边界开始,即段地址 =
X X X X, X X X X, X X X X, X X X X, X X X O B
3) PARA 该段必须从节的边界开始,即段地址 =
X X X X, X X X X, X X X X, X X X X ,0 0 0 0 B
4) PAGE 该段必须从页的边界开始,即段地址 =
X X X X, X X X X, X X X X, 0 0 0 0, 0 0 0 0 B。
注意:当定位类型缺省(即不写)时,隐含值为PARA。
(2)组合类型(combine-type)。在汇编和连接时,当该段与其他段组合在一起时,组合类型用来设定该段与其它段的连接关系,组合类型有以下六种:
1)PUBLIC 该段连接时将与其他同名段依次连接起来,其连接次序由连接程序确 定;
2)COMMON 该段连接时将与其他同名段有相同的段基址.即共享相同的存储空 间.亦即各段会产生覆盖,但可节省内存容量;
3)AT exp 使段基值等于按表达式exp计算所得的16位数。必须指出,对于代码段 不能用ATexp来设定段基值;
4)STACK 仅用于堆栈段,使同名段都从同一段基址开始:
5)MEMORY 指定该段在同名段的最后,即该段在同名段中位于最高的地址空间,若连接时有几个组合类型为MEMORY的段,则只有最前面(最先遇到)的段按组合类型 MEMORY处理,其他段均按组合类型PUBLIC处理;
6)NONE表示本段与其它段逻辑上不发生关系,各段都有自己的段基址。 注意:当组合类型缺省(即不写)时,隐含值为NONE。
(3)‘类别’(‘dass’)。类别必须用单引号括起来。在定位时,连接程序将各程序模块中具有相同类别的逻辑段集中在一起,形成一个统一的物理段。
3.应用命令ORG的偏移地址定位语句
程序中有时需要指定某一语句所在内存单元在段内的偏移地址,这可用ORG命令的 语句来实现,语句的格式为
ORG exp
其中表达式exp,可计算得出16位地址。此时ORG语句的下一个语句所在内存单元 在段内的偏移地址就被指定为按表达式计算得出的16位地址。
例如:
ORG 1000H
MOV AL, BL
本例示明指令MOV AL,BL所在内存单元在本段内的偏移地址为1000H。
4.用命令ASSUME示明段寄存器内容的语句
一个程序通常由很多段组成,对于某一代码段,它所用到的数据段、附加段和堆栈段 只是程序中的某几个有关段,因此在代码段开始时必须用ASSUME命令语句示明该代码 段所用到的段,以便进行汇编。
ASSUME命令语句紧跟在SEGMENT命令语句之后,其格式为
ASSUME CS:段名1,DS:段名2,ES:段名3,SS:段名4
其中段名1、2、3和4分别为该代码段所用到的作为代码段、数据段、附加段和堆栈段的段的段名,亦即将这些段的段基值1、2、3和4作为段寄存器CS、DS、ES S5的内容。
注意:在ASSUME语句中示明的段寄存器和相应的段名是该代码段中实际用到的, 对于该代码段中未用到的段寄存器及相应的段名是不需示明的。
例如:CODE SEGMENT
ASSUME CS:CODE,DS:DATA
CODE ENDS
本例表明在段名为CODE的代码段中,所用到的段寄存器为CS和DS,它们的内容分别是CODE段和DATA段的段基值。
(三)过程定义语句
过程是程序的一部分,即子程序。过程可用程序中的CALL指令调用。当过程中的指令执行完后,用RET指令返回调用它的程序。
应用命令PROC和ENDP的过程定义语句的格式为:
过程名 PROC 类型
┅
RET
过程名 ENDP
过程名是程序员编程时按标识符规定取定的。类型表明该过程是供段内调用,还是供 段间调用,对于前者用NEAR表示,而后者则用FAR表示,当类型项缺省(即不写明)时,隐含值为NEAR。
PROC和ENDP是成对使用的,两语句之间就是该过程的内容(程序),且用RET指令结尾以返回调用它的程序。
成对使用的命令PROC和ENDP的前面必须均写明该过程名。
(四)数据定义语句
数据定义语句用来为数据分配存储单元,例如在内存中设置原始数据以及为存放结果 数据而保留内存单元等。数据段、附加段和堆栈段都是存放数据的,其中所用的语句主要 是数据定义语句。数据定义的命令有DB、DW、DD、DQ和DT等,它们分别用来定义不同类型(长度)的数据。
数据定义语句的格式为
[变量] 命令 参数1,参数2,… ,[;注释]
其中变量是由程序员在编程时按标识符规定取定的,如ARRAY、BUFFER、SUM等,一般都是按照数据的功用取名的。
其中命令的表示符号(助记符)及功能为
DB 定义长度为1字节(8位)的数据(字节数据)。
DW 定义长度为1个Z字(16位)的数据(字数据)。
DD 定义长度为2个字(32位)的数据(双字数据)。
DQ定义长度为8字节(64位)的数据(8字节数据)。
DT 定义长度为10字节(80位)的数据(10字节数据)。
其中参数就是相应内存单元中的数据,它可以是常数(可用各种规定的数制表示)、 字符常数(用单引号括起来的ASCII字符)或符号常数,当它是保留以备存入有关数据 时就以问号(?)表示。参数可以有多个,相互间要用逗号(,)隔开,若连续多个数据是
重复的,就可应用复制符DUP以简化书写,DUP的用法为
复制次数 DUP(数据)
其中数据可以不只一个,且数据还可有复制部分。
例1 段名为DATA的段由以下语句组成
DATA SEGMENT
DATA1 DB 20H
ARRAY DB 12H,12,‘A’
SUM DB ?
DATA ENDS
设本段的段基值为2000H,则相应内存分配为内存
地址 内容
段基值:偏移地址
2000H: 0000H 20H
0001H 12H
0002H 0CH
0003H 41H
0004H ?
其中第1个数据20H在该段的起点,故相应内存单元的偏移地址为0000H,后面数 据所在内存单元的偏移地址依次类推。
其中所有内存单元的段基值都是相同的(=2000H),其中内容的数据都是以16进制 表示的(注:由汇编程序在汇编时进行转换)。
其中“?”表示保留,实际该内存单元的内容为随机数,但对本程序来说,目前是无效的。
例2 DATA2 DB 2DUP(12H,34H,56H)
此时内存分配为
注:未列出具体地址
例3 DATA3 DB ‘ABCD’
其中参数部分‘ABCD’是‘A’,‘B’,‘C’,‘D’的简写。
例4 DATA4 DW 1234H,5678H,9AH,?
其内存分配为
用DW定义的是字数据,每个数据分配2个内存单元,如数据9AH实则上是 009AH。
例5 DATA5 DW ‘AB’,‘CD’
其内存分配为
注意:不能写为DW ‘CDAB’,因为汇编语言语法规定除用DB定义的字符串常量 外,单引号中ASCII字符的个数不得超过2个,若只有1个,例DW‘C’,就相当于DW 0043H。
例6 DATA6 DD 12345678H,‘AB’
其内存分配为
例7 STAK DB 100 DUP(?) ;保留100个字节内存单元作为堆栈区
(五)符号定义语句
符号定义语句的命令有EQU、= 和PURGE
1.应用命令EQU和PURGE的符号定义语句
应用EQU命令的语句的格式为
名字 EQU exp
其中名字是程序员取定的,表达式exp可以计算得出一个具体的数值。这实际上就是给名字赋值,在程序中就可引用这个名字来表示表达式的实际计算值。
若需对已赋值的名字撤消原赋值并赋以新值。则需先用PURGE命令语句撤消原赋值,再用EQU命令语句赋新值。PURGE命令语句的格式为
PURGE 名字
其中名字可不只一个,即可同时撤消几个已赋值。
例如:COUNT EQU 20 ;给COUNT赋值为20
┅
MOV AL,COUNT ;即MOV AL,20
┅
PURGE COUNT ;撤消原赋值
COUNT EQU 10 ;给COUNT赋新值为10
┅
MOV BL,COUNT ;即MOV BL,10
┅
2.应用命令=的符号常量定义语句,其格式为
名字 = exp
命令 = 的功能与EQU类似,唯一的差别是命令;可随时对名字(符号常量)赋新值,而不必使用PURGE命令。
如上例,可写为
COUNT = 20
┅
MOV AL,COUNT
┅
COUNT = 10
┅
MOV BL,COUNT
┅
(六)名字和变量
1.名字
前面已讨论过的名字有文件名、标题名、段名、过程名和符号常量等,它们都是程序 员编程时按标识符规定来命名的,其中有些名字可在编程时引用,以方便编程。
(1) 段名。段名是在源程序的段定义语句中命名取定的,如段定义语句
DATA SEGMENT, 段名为 DATA。
源程序在进行汇编连接时,系统分配给该段一个段基值,设为2000H。这时段名
DATA就可作为段基值2000H被引用。
例如:给段寄存器赋值的指令序列为
MOV AX,DATA ;相当于MOV AX,2000H
MOV DS,AX ;将段基值赋给段寄存器
(2) 过程名。过程名是在源程序的过程定义语句中命名的。如过程定义语句
SORT PROC NEAR,过程名为SORT。
汇编连接源程序时,系统分配给过程一个地址,即该过程第一条指令所在内存单元的 地址,亦即该过程的入口地址,这也就是调用该过程的CAl厶指令中的目的地址。过程名在汇编语言程序中可作为调用指令的目的地址使用,例如指令CALL SORT,其中 SORT就表示过程名为SORT的过程的入口地址,执行该指令就是转移到过程SORT,运行。
(3)符号常量。符号常量是在源程序的符号常量定义浯句中命名取定的,如符号常量定义语句COUNT EQU 20将数值20赋给COUNT。COUNT就可在指令中作为常量20
被引用,如MOV AL,COUNT就相当于MOV AL,20。
例1 部分程序内容为
DATA SEGMENT
ARRAY DB 10H,24H,5AH,0C7H,98H,‘ABCDE’
COUNT EQU $-ARRAY
MAX DB ?
DATA ENDS
其中第3语句行为给符号常量COUNT赋值的语句,句中表达式为$-ARRAY,其计算值就是赋给COUNT的常量数值。下面先讨论表达式中$和ARRAY的含义及具体值,然后由表达式计算出赋给COUNT的具体数值。如本小节(四)所讨论,变量ARRAY所在语句中的第一个数据10H所在内存单元的偏移地址为0000H,也就是该数组的起始地址,后续数据依次存在后续偏移地址的内存单元中,最后一个数据‘E’所在内存单元的偏移地址为0009H。变量ARRAY所在语句的起始偏移地址为0000H,这就是变量 ARRAY的偏移地址属性(注:变量的属性将在下面详细讨论)。由于ARRAY语句的末 地址为0009H,故下一语句行(COUNT语句行)所在的偏移地址就是000AH,此语句中的 $ 就是该行(当前行)的偏移地址,即000AH。故表达式可计算得出为
$-ARRAY=000AH-0000H=000AH=10
因而赋给符号常量COUNT的值为10,COUNT的英文含义为计数,它实际上表示的是ARRAY数组的数据元素个数,即10十字节数据。在程序中用MOV CX,COUNT 指令来设置计数器CX的初值,然后就可用对CX进行减一计数的方法来控制对10个数据的处理。
例2 部分程序内容为
STACK SEGMENT
STAK DB 100 DUP(?) ;保留100个内存单元(字节)作堆栈区
TOP EQU $—STAK ;给TOP赋值为100
STACK ENDS
CODE SEGMENT
┅
MOV AX,STACK ;将段基值STACK赋给段寄存器SS
MOV SS,AX
MOV SP,TOP ;设置堆栈指针
此时,内存中堆栈段的分配如下:
2.变量
(1)变量的定义和属性。如前所述,变量是数据定义语句中的一项,它是由程序员在 编程时按照标识符规定取定的。
当在数据定义语句的第一项对变量命名后,该变量就是已定义了的,已定义的变量具 有下列五种属性:
1)段属性
表示格式: SEG 变量
它表示变量所在段的段基值。
2)偏移地址属性
表示格式: OFFSET 变量
它表示变量所在处的偏移地址。
3)类型属性
表示格式: TYPE 变量
它表示变量所在内存数据的类型。数据类型有字节、字、双字、8字节和10字节,是在该语句中用命令DB、DW、DD、DQ和DT予以定义的。对于不同的数据类型,变量的类型属性具有如下不同的值:
字节数据时, TYPE=1
宇数据时, TYPE=2
双字数据时, TYPE=4
8字节数据时, TYPE=8
10字节数据时,TYPE=10
4)长度属性
表示格式: LENGTH 变量
它表示变量所在数组的数据元素个数。
需注意,只有当数据用复制符 DUP 定义时,LENGTH才等于数组的元素个数,否则
LENGTH就等于1。
5)规模属性 、
表示格式:SIZE变量
它表示变量所在数组的字节总数,且
SIZE:LENGTH*TYPE。
同以上LENGTH的情况,只有当数据用复制符DUP定义时,LENGTH才等于数组的元素个数,否则LENGTH就等于1。
这些属性,程序员编程时均可引用,从而方便编程。
例如:部分程序内容为
DATA SEGMENT
BUFl DB NI,N2,N3,…,N10 ;N1~N10为10个字节数据
BUF2 DB 10 DUP(0)
BUF3 DW 10 DUP(?)
DATA ENDS
设该段的段基值为2000H,则
SEG BUF1=2000H
OFFSET BUF1=0000H
TYPE BUF1=1
LENGTH BUF1=1
SIZEBUF1=1
SEGBUF2=2000H
OFFSETBUF2=000AH
TYPEBUF2=1
LENGTH BUF2=10
SIZEBUF2=10
SEGBUF3=2000H
OFFSETBUF3=0014H
TYPE BUF3=2
LENGTHBUF3=10
SIZE BUF3=20
这些属性在程序中的应用举例
MOV AX,SEG BUFl ;设置段寄存器DS
MOV DS,AX
MOV SI,OFFSET BUFl ;设置地址指针SI
MOV CX,LENGTH BUF2 ;设置计数器CX
MOV BL,SIZEBUF3 ;设置计数器BL
MOV AL,BUF1 ;从内存取数据到寄存器AL。此指令是指令
;MOV AL,OFFSET BUFl]的简便写法,是汇编程序能接受的。
;本指令的功能是将数据N1送到寄存器AL,
MOV AH,BUFl+2 ;这是MOVAH,[OFFSETBUFl+2]的简便写法,指令的功能是将
;数据N3送到寄存器AH
(2)属性运算符。如前所述,定义了的变量具有一定的属性。对变量属性的引用可方 便程序员的编程工作。但变量的类型属性有时会限制它的应用。
设数据定义语句为:
BUFW DW 1234H,5678H
其中变量BUFW的类型属性为字,编程时可很方便地用指令:
MOV AX,BUFW
将字数据1234H传送到寄存器AX。但若要传送字节数据就有问题了,因为指令 MOV AL,BUFW 是非法的,所以非法是由于指令中的两个操作数AL(字节数据)和BUFW(字数据)的类型不同,这样的指令是不能汇编和执行的。为了解决这个问题,汇编语言提供了属性运算符PTR、THIS和LABEL:
1)类型(重新)指定运算符PTR
格式:类型PTR exp
其中类型可以是BYTE、WORD、DWORD、NEAR和FAR(注:前三个是变量的类型属性,后两个是标号的类型属性);exp是表达式,是存储器操作段,当为变量重新指定类型时exp就是变量名。
运算符PIR的作用是仍按后面的表达式去寻址,不管它原来有无类型或是那一种类 型,PTR定义后,就按PIR前面类型项指定的类型看待,实际上,PTR是给后面的存储器操作数赋予新的前面的数据类型(注:对于标号则为地址类型)。运算符PTR的应用如下:
a.重新指定变量类型
BUFW DW 1234H,5678H
下列指令均为合法的
MOV AX,BUFW ;AX ← 1234H
MOV AL,BYTE PTRBUFW ;AL ← 34H
b.指定内存操作数的类型,下列指令
INC [BX] 非法的,因为基址寻址的内存操作数的类型未示明,无法进行汇编和执行。
用PTR指定类型后,指令就是合法的了,如
INC BYTE PTR [BX]
INC WORD PTR [BX]
c.用EQU和PTR定义一个新的变量
BUFW DW 1234H,5678H
BUFB EQU BYTE PTR BUFW
这使新变量BUFB具有和变量BUFW相同的段属性、偏移地址属性,但两者类型不
同,BUFW类型属性为字,而BUFB类型属性为字节。 此时,下列指令就是合法的。
MOV AX,BUFW ;AX ← 1234H
MOV AL,BUFB ;AL ← 34H
2)属性指定运算符THIS ,格式:
THIS 类型
类似于上节PTR运算符的应用。THIS和EQU一起用来定义一个新变量,它与原变 量具有相同的段属性和偏移地址属性,但类型属性不同。
例如:BUFB EQU THIS BYTE
BUFW DW 1234H,5678H
这样,BUFB和BUFW具有相同的段属性和偏移地址属性,但BUFB的类型属性是字节,此时下列指令都是合法的。
MOV AX,BUFW ;AX ← 1234H
MOV AL,BUFB ;AL ← 34H
注意,BUFB语句和BUFW语句必须是紧邻的,且BUFB语句在BLN语句的前
面。
3)命令LABEL
格式: 变量/标号 LABEL 类型
命令LABEL用来定义其语句中的变量(或标号)的类型属性为语句中设定的类型, 此时变量(或标号)的段属性和偏移地址属性是由该语句的位置确定的。
例如:BUFB LABEL BYTE
BUFW DW 1234H,5678H
则下列指令是合法的
MOV AX,BUFW ;AX ← 1234H
MOV AL,BUFB ;AL ← 34H
注意,BUFB语句和BUFW语句必须是紧邻的,且BUFB语句在BUFW语句的前面。
四、指令性语句
指令性语句由指令组成。指令性语句是构成代码段的基础。指令性语句只是在代码段 中才有,在数据段、附加段和堆栈段中是没有指令性语句的。程序运行时,CPU执行指令性浯句中的指令,而指示性语句在程序运行时,是不由CPU执行的,指示性语句是用来指示汇编程序进行汇编操作的。
指令性语句的格式为
[标号] 操作码 [操作数] [;注释]
其中操作码和操作数就是指令,本书第三章已讨论了8086/8088指令系统,此处不再 赘述。
本节将对标号和操作数进行讨论。
(一)标号
标号是程序员编程时按标识符规定取定的,并常常具有它在程序中的作用的含义,如
NEXT、AGAIN等,并且标号一定要用冒号(:)结尾。
在指令性语句中写上标号后,就定义了该标号,定义了的标号具有下列三种属性。
1 段属性
表示格式:SEG标号
它表示标号所在段的段基址。
2.偏移地址属性
表示格式:OFFSET标号
它表示标号所在位置的偏移地址,即该语句的指令的第一字节所在内存单元的偏移地 址。
3.类型属性
表示格式:TYPE标号
标号通常用作转移指令的目的操作数,即转移去的目的地址。我们知道,转移有 NEAR(近转移,即段内转移)和FAR(远转移,即段间转移)两种,NEAR和FAR是转移的两种类型,因而也就是标号的类型。类型为NEAR的标号供段内转移用,而类型为FAR的标号则供段间转移用。
标号的类型属性和标号的类型有关:
NEAR时,TYPE = -1
FAR时,TYPE = -2
其中的-1和-2投有真正的物理意义,只是以这样的具体数值表示而已(注:由于要和变量的类型属性清楚地区别,所以用的是负值)。
指令性语句中的标号,其类型一般都是NEAR。为了适应段间调用的需要,要把它的类型改换成FAR。与前面变量改变类型相类似,标号类型的改变可应用运算符PTR、THIS和LABEL,可以是重新指定类型,也可以定义一个新标号。
设程序中标号为METER,其类型为NEAR。若定义一个新变量KILOMT,其类型为FAR,而KILOMT的段属性和偏移地址属性则是和METER的相同。这样,段内转移时用METER作为目的操作数,而段间转移时则用KILOMT作为目的操作数,两个标号表示的目的地址是同一个,即均转移到程序中标号为METER处。具体方法如下。
(1)用PTR重新指定类型:
段内转移用指令
JMP METER
段间转移用指令
JMP FAR PTR METER
(2)用EQU和PTR定义新标号KILOMT
METER:┅
KILOMT EQU FAR PTR METER
(3)用EQU和THIS定义新标号KILOMT
KILOMT EQU THIS FAR
METER:┅
(4)用LABEL定义新标号KILOMT
KILOMT LABEL FAR
METER: ┅
这四种方法和前面变量改变类型的方法是一样的,这里不赘述。
(二)操作数
指令中的操作数可按寻址方式表示,寻址方式在前面第三章中已讨论,此处不再赘述。 操作数也可以用段名、符号常量、变量、属性、过程名和标号来表示,如下例所示
MOV AX,DATA ;DATA是段名,立即寻址方式
MOV CX,COUNT ;COUNT是符号常量,立即寻址方式
MOV BL,BUFFER ;BUFFER是变量,直接寻址方式
MOV SI,OFFSET ARRAY ;OFFSETARRAY是属性,立即寻址方式
CALL SBRT1 ;SBRT1是过程名。直接寻址方式
JMP DONE ;DONE是标号,直接寻址方式
LOOP AGAIN ;AGAIN是标号,直接寻址方式
五、宏指令
程序员用汇编语言编程时,对于程序中多次重复使用的指令序列(即很小的程序段), 可定义一条宏指令,编写程序时就用这条宏指令代替该指令序列,从而简化书写工作:
(一)宏定义、宏名字、宏调用和宏展开
宏定义就是定义宏指令,宏定义的命令是MACRO和ENDM。宏定义的格式为
宏名字 MACRO [形式参数]
┅
ENDM
其中宏名字是宏指令的名字,是程序员按标识符规定取定的。命令MACR0和 ENDM之间的指令序列就是该宏指令的内容,称为宏体。带方括号的形式参数是任选项,当无形式参数时就无该项,当有多个形式叁数时相互间由逗号隔开。形式参数亦称哑参数、哑元或变元,它是宏体中有关指令的操作码、操作数或它们的一部分。形式参数是没有物理童义的,只有用实参数代替形式参数后,相应的指令才有实际意义。
在程序中应用宏指令称为宏调用,宏调用的格式是:
宏名字 [实参数]
其中实参数是与宏定义中的形式参数一一对应的。
汇编时,汇编程序用已由实参数取代形式参数的宏体取代程序中的宏指令,称为宏展 开,此时程序中已全部是可执行的指令序列了。
例1 无形式参数的宏指令
宏定义 SAVEREG MACRO
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
ENDM
宏指令名为SAVEREG,功能是有关寄存器内容进栈保护现场。
宏调用
程序
┅
SAVEREG
┅
SAVEREG
宏展开
程序
┅
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
┅
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
汇编程序再将此宏展开后的程序转换成机器码生成目标程序,
例2 带有形式参数的宏指令
宏定义MULTIPLY MACRO OPRl,ORR2,RESULT
PUSH AX
MOV AL,OPRl
1MUL OPR2
MOV RESULT,AX
POP AX
ENDM
其中有三个形式参数OPRl、OPR2和RESULT,它们都是宏体指令中的操作数。
宏调用程序
MULTIPLY CL,VAR,XYZ[BX] ;实参数CL是寄存器,VAR是变量,
XYZ[BX]是基址寻址的内存操作数,
MULTIPLY 240,BL,SAVE ;实参数240是立即数,BL是寄存器,
SAVE是变量
宏展开程序
PUSH AX
MOV AL,CL
IMUL VAR
MOV XYZ[BX],AX
POP AX
┅
PUSH AX
MOV AL,240
IMUL BL
MOV SAVE,AX
POP AX
┅
汇编程序将此宏展开后的程序转换成机器码生成目标程序。
(二)宏指令与子程序的差别
如上所述,宏指令是将一段程序(指令序列)用一条宏指令来代替,以简化书写源程 序。子程序(过程)也有类似的功能,但两者是有差别的,具体如下:
(1) 宏指令简化了源程序的书写。但在汇编时,汇编程序对宏指令的汇编处理是将宏指
令的宏体(即程序段)原原本本地插入到宏指令调用处,然后转换成机器码生成目标程 序。因此,宏指令虽简化了源程序,但并没有简化目标程序,有多少次宏调用,在目标程 序中就有同样多次数的目标代码插入。所以宏指令不节省目标程序需占用的内存单元。
子程序(过程)在执行时是由CPU用调用(CALL)来处理的。若在一个源程序中多次调用同一个子程序,则在目标程序中,主程序中只有调用(CALL)指令的目标代码,CALL指令的目标代码只有几个字节,该目标代码出现的次数就是调用次数。而子程序的目标代码在整个目标程序中只出现一次,所以相应地其目标程序就占用较少的内存单元,即可节省内存单元。
(2)采用子程序方式时,每调用一次就需执行一次CALL和RET指令,而宏指令方式时,并无此两条指令。因此,使用宏指令时的程序执行时间比子程序时的程序执行时间要短一,即宏指令时程序执行速度快。
由上可知,宏指令和子程序各有特点,宏指令执行速度快而子程序占用内存少。一 般,对于程序段较长的情况,采用子程序可节省很多内存而对执行速度影响不大;对于程 序段较短的情况,采用宏指令可加快速度而对增加占用内存容量影响不大,尤其对于程序 段较短而形式参数较多的情况,宏指令就更能显示其突出的优点了。
本文标签:山东自考 工学类 微型计算机原理及应用学习笔记 汇编语言的基本
转载请注明:文章转载自(http://www.sdzk.sd.cn)
《山东自考网》免责声明:
1、由于各方面情况的调整与变化,本网提供的考试信息仅供参考,考试信息以省考试院及院校官方发布的信息为准。
2、本站内容信息均来源网络收集整理,标注来源为其它媒体的稿件转载,免费转载出于非商业性学习目的,版权归原作者所有,如有内容与版权问题等请与本站联系,本站将第一时间尽快处理删除。联系邮箱:812379481@qq.com。
相关《微型计算机原理及应用学习笔记 汇编语言的基本》的文章