第 4章 汇编语言程序设计本章学习目标通过本章的学习,应当掌握以下内容:
了解汇编语言的基本知识和特点。
熟悉汇编语言的程序结构、段定义以及语句的格式。
掌握汇编语言常用伪指令的使用方法。
熟练掌握汇编语言程序设计的基本方法:顺序结构、
分支结构、循环结构和子程序结构。
掌握程序设计中的宏指令和常用的系统功能的调用方法。
4,1 机器语言、汇编语言与高级语言程序设计语言通常分为 3类:
机器语言( Machine Language)
汇编语言( Assembler Language)
高级语言( High Level Language)
4,1,1 机器语言和汇编语言
1.机器语言
( 1)机器指令机器指令 是指用二进制编码的指令,以表示计算机所要进行操作数及操作对象(数据或数据地址)。
( 2)指令系统和机器语言指令系统 是指特定计算机上机器指令的集合。机器语言是由指令系统以及机器指令的使用规则构成的。
机器语言 是计算机惟一能识别的语言,只有用机器语言描述的程序,计算机才能直接执行。
( 3)机器语言的主要特点机器语言主要具有下列两个特点:
① 机器语言与机器密切相关
② 机器语言设计程序非常困难,但容易实现高性能
2,汇编语言以助记符描述的指令称作汇编格式指令或符号指令,
通常简称 指令 。指令和伪指令的集合及其程序设计规则便构成了 汇编语言 。用汇编语言编写的程序就是 汇编语言源程序 。
4,1,2 汇编语言与高级语言机器语言和汇编语言 都是面向机器的,是低级语言。
高级语言 在程序设计的简易性与代码的可移植性等方面有了质的飞跃。当然,用高级语言编写的源程序必须经过编译和连接,将其转变为可执行程序或借助于解释程序方可在计算机上运行。
语言 汇编语言 高级语言代码效率 高 较低源程序可读性 较差 好对硬件的依附性 高 低程序员硬件知识 高 较低应用范围 较广 广泛汇编语言和高级语言的比较:
4,1,3 汇编与连接
1.汇编程序汇编是把汇编语言程序翻译成机器语言描述的目标程序的过程。
汇编程序是完成汇编任务的程序。
2.连接程序连接程序的主要功能是实现多个目标文件及库文件的连接,并完成浮动地位的重定位。
从汇编语言源程序到可执行程序的生成过程如图所示。
汇编语言源程序 汇编 目标程序连接可执行程序
4,2 汇编语言源程序的结构
4,2,1 汇编语言的语句格式汇编语言源程序中的每个语句可以由 4项组成,格式如下
[name] operation operand [; comment]
[名字项 ] 操作项 操作数项 ;注释项下面分别说明各项的表示方法。
1.名字项源程序中用下列字符表示名字:
字母,A~ Z或 a~ z;
数字,0~ 9;
专用字符号:?,?,@,―,$;
一般来讲,名字项可以是标号或变量。
( 1)标号:在代码段定义,后面跟冒号:
它有三种属性:段、偏移及类型。
① 段属性:定义标号的段起始地址,在 CX寄存器中。
② 偏移属性,16位无符号数。
③ 类型属性:用来指出该标号是在本段内引用还是在其他段内引用的。
( 2)变量:变量在除代码以外的其他段中定义,后面不跟冒号。它也可以用 LABLE或 EQU伪操作来定义。变量经常在操作数字段出现。
它也有段、偏移及类型三种属性。
① 段属性定义变量的段起始地址,此值必须在一个段寄存器中。
② 偏移属性变量的偏移地址是 16位无符号数,它代表从段的起始地址到定义变量的位置之间的字节数。在当前段内给出变量的偏移值等于当前地址计数器的值,当前地址计数器的值可以用 $ 来表示。
③ 类型属性变量的类型属性定义该变量所保留的字节数。
2.操作项操作项可以是指令、伪操作或宏指令的助记符。
3.操作数项操作数项由一个或多个表达式组成,多个操作数项之间一般用逗号分开。
4.注释项注释项用来说明一段程序或一条或几条指令的功能,它是可有可无的。
4,2,2汇编语言源程序的段定义段定义伪操作的格式如下:
segment-name SEGMENT

segment-name ENDS
其中删节号部分,对于 DS,ES和 SS来说,一般是存贮单元的定义、分配等伪操作;对于代码段则是指令及伪操作。
此外,还必须明确段和段寄存器的关系,这可用
ASSUME伪操作来实现,其格式为:
ASSUME assignment,…,assignment
其中 assignment 说明分配情况,其格式为:
〈 段寄存器名 〉,〈 段名 〉
其中段寄存器名必须是 CS,DS,ES和 SS中的一个,
而段名则必须是由 SEGMENT定义的段中的段名。 4、
2,.3 汇编语言源程序的结构汇编语言源程序的程序基本结构是段,一个汇编语言源程序由若干个代码段、数据段、附加段和堆栈段组成。段之间的顺序可以随意安排,通常数据段在前,
代码段在后。任何可执行汇编语言程序至少要有一个代码段,通常还可能有数据段和堆栈段。每个段都有段首指令和段结束指令,段的内容介于这两条指令之间。
其一般结构如下,
SSEG SEGMENT STACK
〈 堆栈段的内容 〉
SSEG ENDS
DSEG SEGMENT DATA
〈 数据段的内容 〉
DSEG ENDS
CSEG SEGMENT CODE
〈 代码段的内容 〉
CSEG ENDS
END 〈 启动标号 〉
对于一般程序来说,定义太多的段只会增加程序设计的复杂性,通常需要一个 代码段,一个 数据段 和一个堆栈段,有时可包含一个附加段。
1.为什么要用 ASSUME语句指令
mov bl,xd
mov xe,bl
被汇编为
mov bl,byte ptr[0000h]
mov byte ptr es:[0000h],bl
其中,在变量 xe前增加了段超越前缀 ES:,这就是
ASSUME的作用,也仅此而已。
2.设置段寄存器的初值
( 1) CS与 IP
CS与 IP的初值不能在程序中显示设置,由系统自动设置为 END后指定的起始地址。
( 2) DS和 ES、
DS和 ES的初值必须在程序中设置。
设置方法如下:
MOV AX,SEG NAME
MOV DS,AX
( 3) SS与 SP
SS与 SP初值的设置方法有下列两种:
在程序中显示设置,类似于 DS,例如:
MOV AX,SSEG
MOV SS,AX
MOV SP,ST_TOP
若堆栈段定义时给出了参数 STACK,则连接器 LINK
自动将 SS,SP指向栈底;
若未定义堆栈段,则由系统指定堆栈,SS,SP也由系统自动设置。
[例 4.2]:在数据段定义首地址为 A的 10个字符,将这 10
个字符以相反次序传送到附加段首地址为 B的内存单元中。
DSEG SEGMENT
A DB?1234567890?
DSEG ENDS
ESEG SEGMENT
B DB 10 DUP(?)
ESEG ENDS
CSEG SEGMENT
ASSUME CS,CSEG,DS,DSEG,
ES,ESEG
START,MOV AX,DSEG
MOV DS,AX
MOV AX,ESEG
MOV ES,AX
LEA SI,A
LEA DI,B
ADD DI,9
MOV CX,10
MOVE,CLD
LODSB
STD
STOSB
LOOP MOVE
MOV AH,4CH
INT 21H
CSEG ENDS
END START
4,3 汇编语言的运算符
1.算术运算符算术运算符主要包括 +、-,*,/和 MOD,[]等。其中
MOD是指除法运算后得到的余数。
语法格式为:
expr1[expr2]等价于 expr1+expr2。
2.逻辑运算符逻辑运算符主要包括 AND,OR,XOR,NOT,SHR及
SHL。逻辑操作符是按位操作的,它只能用于数字表达式中。
3.关系运算符关系运算符主要包括 EQ(相等时为真),NE(不相等时为真),LT(小于时为真),GT(大于时为真)、
LE(小于或等于时为真),GE(大于或等于时为真)
等 6种。
4.数值回送( Value returning)操作符它有 TYPE,LENGTH,SIZE,OFFSET,SEG 5种。
这些操作符把一些特征或存储器地址的一部分作为数值回送。下面分别说明各个操作符的功能。
( 1) TYPE
格式,TYPE Variable或 label
如果是变量,则汇编程序将回送该变量的以字节数表示的类型,DB为 1,DW为 2,DD为 4,DQ为 8,
DT为 10。
如果是标号,则汇编程序将回送代表该标号类型的数值,NEAR为- 1,FAR为- 2。
( 2) LENGTH
格式为,LENGTH Variable
对于变量中使用 DUP的情况,汇编程序将回送分配给该变量的单元个数。而对于其他情况则回送 1。
( 3) SIZE
格式为,SIZE Variable
其汇编的值为 LENGTH Variable * TYPE Variable,即回送直接分配给该变量的总的字节数。
( 4) OFFSET
格式为,OFFSET Variable或 label
汇编程序将回送变量或标号的偏移地址值。
( 5) SEG
格式为,SEG Variable或 label
汇编程序将回送变量或标号的段地址值。
5.属性操作符属性操作符主要包括 PTR、段操作符,SHORT,THIS、
HIGH和 LOW 6种。
( 1) PTR
格式为,type PTR expression
PTR用来建立一个符号地址,但它本身并不分配存储器,只是用来对已分配的存储地址赋予另一种属性,
使该地址具有另一种类型。
( 2)段操作符:
用来表示一个标量、变量或地址表达式的段属性。例如,用段前缀指定某段的地址操作数 MOV AX,ES:
[BX+SI]。可见它是用段寄存器地址表达式来表示的。
( 3) SHORT
用来修饰 JMP指令中转向地址的属性,指出转向地址是在下一条指令地址 ± 127个字节范围之内。
( 4) THIS
格式为,THIS attribute或 type
( 5) HIGH和 LOW
称为字节分离操作符,它接收一个数或地址表达式,
HIGH取其高位字节,LOW取其低位字节。
4.4 伪指令构成汇编语言源程序的语句主要包括两类:
指令和伪指令。
指令 是在程序运行期间有 CPU执行的,汇编后由对应的机器代码所取代。
伪指令 是不可执行的,它只是在源程序汇编期间由汇编器处理的命令,用来指示汇编器为数据分配内存空间,
或者为汇编器提供源程序结束或段定义等信息。
1,变量定义伪指令变量定义伪指令用来为数据分配内存空间,并设置相应内存单元的初始值,其形式为:
[变量名 ] 变量定义符 操作数 [,…,操作数 ]
变量名是一个符号地址,表示其后操作数的首地址,
多个操作数构成一个数组。变量名是程序员给出的标识符,为可选项,给出变量名只是为了按名存取其对应的内存单元。
变量定义符主要包括下列几种:
( 1) DB( Define Byte):定义字节,后面的每个操作数占 1个字节。
( 2) DW( Define Word):定义字,后面的每个操作数占 1个字。
( 3) DD( Define DWord):定义双字,后面的每个操作数占 2个字。
( 4) DQ( Define QWord):定义四字,后面的每个操作数占 4个字。
( 5) DT( Define Tbyte):定义十字节,后面的每个操作数占 10个字节。
操作数可以为:
( 1)数值表达式;
( 2) ASCⅡ 码字符串;
( 3)地址表达式;
( 4)?(只保存内存空间,未定义初始值);
( 5) DUP 子句,其格式为,重复次数 DUP (操作数,…,操作数),DUP子句可以嵌套。
2、符号定义伪指令符号定义伪指令的基本形式为:
符号名 EQU 表达式符号名 = 常数表达式功能:给表达式指定一个等价的符号名。
说明:
( 1) =后的表达式只能是常数,对于字符或字符串,在汇编时按整数处理。
( 2) EQU后的表达式可以是数值、字符串,甚至可以是寄存器名、指令的助记符等。
( 3) EQU不能重复定义,而,=”伪指令可以重复定义,
其作用域从定义点到重新定义之前。
3、段定义伪指令汇编语言源程序由一个段或多个段组成,多数程序采用一个代码段、一个数据段和一个堆栈段。段定义由
SEGMENT与 ENDS伪指令实现,基本形式如下:
段名 SEGMENT [STACK]
〈 语句序列 〉
段名 ENDS
4、程序开始和结束伪操作在程序的开始可以用 NAME或 TITLE为模块的名字,
NAME的格式是:
NAME module_name作为模块的名字。如果程序中没有
NAME 伪操作,则也可使用 TITLE伪操作,其格式为:
TITLE text
表示源程序结束的伪操作的格式为:
END 〈 启动标号 〉
其中标号指示程序开始执行的起始地址。如果多个程序模块相连接,则只有主程序要使用标号,其他子程序模块则只用 END而不必指定标号。
这里要说明两点:
( 1)其中关于建立过程的 PROC和 ENDP伪操作对将在以后的章节中说明。这里只要知道利用这一对伪操作把程序段分为若干个过程,使程序的结构更加清晰就可以了。
( 2)这里只定义了最基本的代码段和数据段,如果程序中还需要定义附加段和堆栈段,则定义的方式及建立段寄存器的方法是相同的,读者可自行设计。
5,对准伪操作
( 1) EVEN伪操作使下一个字节地址成为偶数。一个字的地址最好从偶地址开始,所以对于字数组为保证其从偶地址开始,可以在它前面用 EVEN伪操作来达到此目的。例如:
DATA_SEG SEGMENT

EVEN
WORD_ARRAY DW 100 DUP(?)

DATA_SEG ENDS
( 2) ORG 〈 表达式 〉
如常数表达式的值为 n,则 ORG伪操作可以使下一个字节的地址成为常数表达式的值 n。其中表达式必须是一个可计算得到正整数的,数值范围在 0~ 65535的表达式。
4,5 宏指令与条件汇编
1.宏定义和宏调用宏是源程序中一段有独立功能的程序代码。它只需要在源程序中定义一次,就可以我次调用它,调用时只需要有一个宏指令语句就可以了。
宏定义是用一组伪操作来实现的。其格式是:
macro name macro [dummy parameter list]
┇ (宏定义体 )
ENDM
经宏定义定义后的宏指令就可以在源程序中调用。
这种对宏指令的调用称为宏调用,宏调用的格式是:
macro name [actual parameter list]
当源程序被汇编时,汇编程序将对每个宏调用作宏展开。
宏展开就是用宏定义体取代源程序中的宏指令名,而且有实元取代宏定义中的哑元。
下面我们用一个例子来说明宏定义、宏调用和宏展开的情况。
[例 ] 用宏指令定义两个字操作数相乘,得到一个 16位的第三个操作数,作为结果宏定义:
MULTIPLY MACRO opr1,opr2,RESULT
PUSH DX
PUSH AX
MOV AX,opr1
IMUL opr2
MOV RESULT,AX
POP AX
POP DX
ENDM
宏调用:

MULTIPLY CX,VAR,XYZ[BX]

MULTIPLY 240,BX,SAVE

宏展开:
+ PUSH DX
+ PUSH AX
+ MOV AX,CX
+ IMUL VAR
+ MOV XYZ[BX],AX
+ POP AX
+ POP DX

+ PUSH DX
+ PUSH AX
+ MOV SAVE,AX
+ POP AX
+ POP DX

2,宏指令举例
[例 ] 宏定义可以无变元宏定义:
SAVEREG MACRO
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
ENDM
宏调用:
SAVEREG
宏展开则将宏定义体的内容全部列出。
3,条件汇编汇编程序能根据条件把一段源程序包括在汇编语言程序内或者把它排除在外,这里就用到条件伪操作。条件伪操作的一般格式是:
IF ×× argument
┇ }自变量满足给定条件汇编此块
[ELSE]
┇ } 自变量不满足给定条件汇编此块
ENDIF
自变量必须在汇编程序第一遍扫视后就成为确定的数值,
条件伪操作中的 ×× 表示条件如下:
IF expression 汇编程序求出表达式的值,如此值不为 0则满足条件
IFE expression 如求出表达式的值为 0则满足条件。
IFDEF symbo1 如符号已在程序中定义,
或者已用 EXTRN伪操作说明该符号是在外部定义的,则满足条件。
IFNDEF symbo1 如符号未定义或未通过
EXTPN说明为外部符号则满足条件。
IFB <argument> 如自变量为空则满足条件。
IFNB <argument> 如自变量不为空则满足条件。
IFIDN <arg—1>,<arg—2> 如果字符串 <arg—1>和字符串 <arg—2>相同,则满足条件。
IFDIF <arg—1>,<arg—2> 如果字符串 <arg—1>和字符串 <arg—2>不相同,则满足条件。
4,6 基本结构程序设计一般说来,编制一个汇编语言程序的步骤如下:
( 1) 分析题意确定算法。这一步是能否编制出高质量程序的关键,因此拿到题应该仔细地分析和理解题意,
找出合理的算法及适当的数据结构,不应当急于的去写程序。
( 2) 根据算法画出程序框图。这点对初学者特别重要,
这样做可以减少出错的可能性。画图可以从粗到细把算法逐步地具体化。
( 3) 根据框图编写程序。
( 4)上机调试程序。
程序有顺序、循环、分支和子程序 4种结构形式。
4,6,1 顺序结构顺序程序设计,又叫直接程序设计。它是相对于分支程序和循环程序设计而言的。因此,可以说顺序程序是既不包含分支,又不包含循环的程序,顺序程序是从第一条指令开始,按其自然顺序,一条指令一条指令地执行,
在运行期间,CPU既不跳过某些指令,也不重复执行某些指令,一直执行到最后一条指令为止。
[例 ]:从键盘键入 0至 9中任一自然数 X,求其立方值。
求一个数的立方值可以利用乘法和查表方法来实现,在本例中利用查表方法来实现。构造一个立方表,事先将
0至 9的立方存放在表中,求 0至 9的立方值可直接从表中查出。
表存储单元分配:字节变量 X存放键入的自然数 X,字变量 XXX中存放 X的立方值。从表结构可知,X的立方值在表中的存放地址与 X有如下对应关系:
( TAB+2*X) =X的立方值源程序如下:
STACK SEGMENT STACK
DB 200 DUP ( 0)
STACK ENDS
DATA SEGMENT
INPUT DB,PLEASE INPUT X ( 0… 9),$”
TAB DW 0,1,8,27,64,125,216,343,512,
729
X DB?
XXX DW?
DATA ENDS
CODE SEGMENT
ASSUME CS,CODE,DS,DATA,SS,STACK
START,MOV AX,DATA
MOV DS,AX
MOV AH,9
LEA DX,INPUT
INT 21H
MOV AH,1
INT 21H
AND AL,OFH
MOV X,AL
ADD AL,AL
MOV BL,AL
MOV BH,0
MOV AX,TAB [BX]
MOV XXX,AX
MOV AH,4CH
INT 21H
CODE ENDS
END START
4.6.2 分支结构
1.分支程序设计概述分支程序结构可以有两种形式,如图所示判断条件
Y N
判断条件
……
2、分支程序设计分支结构程序设计的关键在于准确地知道操作结果影响的标志位状态和正确地使用条件转移指令。根据对条件的判断而选择不同的处理方法是人的基本智能体现。
[例 ]:设内存中有三个互不相等的无符号字数据,分别是放在 ARG开始的字单元,编制程序将其中最大值存入 MAX单元。
分析:求三个无符号数中的最大值,只要把三个数据两两比较,用 JA/JNB/JNA/JC等指令就可判断两数的大小,
从而选出其中最大值。
源程序如下:
SSEG SEGMENT STACK
STK DB 20 DUP ( 0)
SSEG ENDS
DSEG SEGMENT
ARG DW 7138H,84A6H,29EH
MAX DW?
DESG ENDS
CSEG SEGMENT
ASSUME CS,CSEG,DS,DSEG,SS,SSEG
FMAX,MOV AX,DSEG
MOV DS,AX
MOV SS,AX
MOV SP,SIZE STK
LEA SI,ARG
MOV AX,[SI]
MOV BX,[SI+2]
CMP AX,BX
JAE FMAX1
MOV AX,BX
FMAX1,CMP AX,[SI+4]
JAE FMAX2
MOV AX,[SI+4]
FMAX2,MOV MAX,AX
MOV AH,4CH
INT 21H
CSEG ENDS
END FMAX
4,6,3 循环程序设计
1.循环程序设计概述有时我们会需要能按一定规律,多次重复执行的一串语句,这类程序叫循环程序。
循环程序一般由四个部分组成:
( 1)循环初值部分:这是为了保证循环程序能正常进行循环操作而必须做的准备工作。循环初值分两类:一类是循环工作部分的初值,别一类是控制循环结束条件的初值。
( 2)工作部分:即需要重复执行的程序段。这是循环的中心,称之为循环体。
( 3)修改部分:按一定规律修改操作数地址及控制变量,以便每次执行循环体时得到新的数据。
( 4)控制部分:用来保证循环程序按规定的次数或特写条件正常循环。
[例 ]:已知道有 N个元素存放在以 BUF为首址的字节存储区中,试统计其中负元素的个数。显然,每个元素为一个 8位有符号二进制数。统计其中负元素的个数的工作可用循环程序实现。
存储单元及寄存器分配如下:
BX,BUF存储区的地址指针,初值为 BUF的偏移地址,
每循环一次之后,其值增 1。
CX:循环计数器,初值为 BUF区中元素的个数 N,每循环一次之后,其值减 1。
AX:用来记录负元素的个数,初值为 0。
字变量 R用来存放负元素的个数。
源程序如下:
STACK SEGMENT STACK
DB 200 DUP( 0)
STACK ENDS
DATA SEGMENT
BUF DB -2,5,-3,6,100
DB 0,-20,-9,8,-110,20
N = $-BUF
R DW?
DATA ENDS
CODE SEGMENT
ASSUME CS,CODE,DS,DATA,SS,STACK
BEGIN,MOV AX,DATA
MOV DS,AX
LEA BX,BUF
MOV CX,N
MOV AX,0
LOPA,CMP [BX],BYTE PTR 0
JGE NEXT
INC AX
NEXT,INC BX
DEC CX
JNE LOPA
MOV R,AX
MOV AH,4CH
INT 21H
CODE ENDS
END BEGIN
2、循环程序设计
( 1)循环的控制方法下面介绍最常见的两种控制方法:计数控制和条件控制。
① 计数控制当循环次数已知时,通常使用计数控制法。假设循环次数为 n,常常用以下三种方法实现计数控制和条件控制先将循环次数 n送入循环体计数器中,然后,每循环一次,计数器减 1,直至循环计数器中的内容为 0时结束循环。如:
MOV CX,n
… ;循环初值部分
LOOPA,… ;工作部分
… ;修改部分
DEC CX ;控制部分
JNZ LOOPA:
其中工作部分、修改部分被重复执行 n次,即当( CX)
=n,n-1,…,1时,重复执行循环体,当( CX) =0
时,结束循环。
先将循环次数的负值送入循环计数器中,然后每循环一次,计数器加 1,直至计数器中的内容为零时结束循环。例如:
MOV CX,- n
… ;循环初值部分
LOOPA,… ;工作部分
… ;修改部分
INC CX ;控制部分
JNZ LOOPA
其中工作部分、修改部分被重复执行 n次,即当( CX)
=- n,- (n- 1),…,- 1时重复执行,当( CX) =0时结束循环。
先将 0送入循环计数器中,然后每循环一次,计数器加 1,
直到循环计数器的内容与循环次数 n相等时退出循环。
例如:
MOV CX,0
… ;循环初值部分
LOOPA,… ;工作部分
… ;修改部分
INC CX ;修改部分
CMP CX,n
JNE LOOPA
其中工作部分、修改部分重复执行 n次,即当( CX) =0,
1,…,n- 1时重复执行,当( CX) =n时结束循环。
② 条件控制有些情况下,循环次数事先无法确定,但它与问题的某些条件有关。这些条件可以通过指令来测试。若测试比较的结果表明满足循环条件,则继续循环,否则结束循环。
[例 ]:统计 AX寄存器中 1的个数,并将结果存放在 CL寄存器中。 …
MOV CL,0
L,AND AX,AX
JZ EXIT
SAL AX,1 ;将 AX中的最高位移入 CF中
JNC L ;如果 CF=0,转 L
INC CL ;如果 CF=1,则( CL) +1→CL
JMP L ;转 L处继续循环
EXIT,…
2、单重循环程序设计所谓单重循环指循环体内不再包含循环结构。下面分循环次数已知和未知两种情况讨论其程序设计方法。
( 1)循环次数已知的循环程序设计对于循环次数已知的情况,通常采用计数控制方法来实现循环。
( 2)最大循环次数未知的循程序设计对于循环次数未知的情况,常用条件来控制循环。
3、多重循环程序设计多重循环即循环体内套有循环。设计多重循环程序时,
可以从外层循环到内层循环一层一层地进行。
4,6,4 子程序设计
1.子程序设计概述
( 1)子程序定义伪指令子程序定义伪指令有两条,PROC和 ENDP
使用这两条子程序定义伪指令所定义的子程序的一般格式如下:
PN PROC [NEAR]/[FAR] ;说明过程开始
… ;过程体
PN ENDP ;说明过程结束说明:
① 伪指令一前一后,如同过程体的语句括号,说明过程的开始与结束,中间为过程体。这两条伪指令必须成对出现。
② N为过程名,有以标识不同的过程。过程名的命名原则与标号相同,一般在程序设计风格上有所讲究。给过程命名时最好用一个使人容易理解的过程名。
③ 过程体即为一段独立的程序,是完成子程序功能的程序主体。除了不能使用 HLT等引起停机的指令而必须使用返回指令作为逻辑上最后一条指令之外,与以前所讲的程序设计没有任何区别。也就是说,过程体中可以使用除引起停机的指令以外的任何指令。
④ EAR和 FAR分别指出所定义的过程是近过程还是远过程。近过程只允许段内调用,即在转返过程中保持当前
CS不变,只允许本段内程序使用。 NEAR和 FAR都不写时默认所定义的过程为近过程。
[例 ]:在代码段 SEG1段定义两个过程 SUB1和 SUB2,
并使 SUB1只为本段程序调用,SUB2可为其他代码段程序调用。程序的过程定义部分如下所示:
SEG1 SEGMENT PUBLIC

SUB1 PROC NEAR

SUB1 ENDP
SUB2 PROC FAR

SUB2 ENDP
SEG1 ENDS
( 2)调用指令 CALL
( 3)返回指令 RET
2、子程序程序设计
( 1)子程序的嵌套我们已经知道,一个子程序也可以作为调用程序去调用另一个子程序,这种情况就称为子程序的嵌套。嵌套的层次不限,其层数称为嵌套深度。
( 2)递归子程序在子程序嵌套的情况下,如果一个子程序调用的子程序就是它自身,这就称为递归调用。这样的子程序称为递归子程序。递归子程序对应于数学上对函数的递归定义,
它往往能设计出效率较高的程序,可完成相当复杂的计算,因此它是很有用的。例如阶乘函数,常常使用递归子程序的设计方法 。
4,7 常用系统功能调用和 BIOS
BIOS是 BASIC INPUT/OUTPUT SYSTEM的缩写,即基本输入 /输出系统,指固化在 ROM中的一组程序,以称 ROM BIOS。 BIOS提供了最低,最直接的硬件控制,
是硬件与软件之间的接口。 BIOS主要包括以下一些功能。
( 1)系统自检及初始化。例如,系统加电启动时对硬件进行检测;对外部设备进行初始化;设置中断向量;
引导操作系统等。
( 2)系统服务。为操作系统和应用程序提供系统服务,
这些服务主要与 I/O设备有关,如读取键盘输入等。为了完成这些操作,BIOS必须直接与 I/O设备打交道,它通过端口与 I/O设备之间传送数据,使应用程序脱离具体的硬件操作。
( 3)硬件中断处理。提供硬件中断服务程序。
下面简要介绍一些较常用的 DOS与 BIOS服务。
1,DOS系统调用
MS DOS使用中断号 21H作为系统说明,为程序员提供了上百种系统服务功能。
对这些功能的调用步骤如下:
( 1)由 AH给出功能号;
( 2)根据相应功能的要求,设置入口参数。入口参数是子程序运行所需要的数据,DOS系统功能调用的入口参数通常是放在指定的内部寄存器器中,少数功能调用也可以没有入口参数;
( 3)执行中断指令 INT 21H;
( 4)分析和使用出口参数。
下面详细介绍字符与字符串的输入 /输出的调用方法
( 1)键盘读入一个字符功能号,01H。
入口参数:无。
出口参数,AL=输入字符的 ASCII码。
功能:等待从键盘读入一个字符,将其 ASCII码送入
AL,同时将字符显示在屏幕上。
调用方法:
MOV AH,1
INT 21H
说明:输入一个字符后,不需要回车。
若只输入回车,则 AL=0DH。
( 2)显示一个字符功能号,02H。
入口参数,DL=要显示字符的 ASCII码。
出口参数:无。
功能:在当前光标位置显示 DL中的字符,光标右移。
调用方法示例:
MOV DL,‘ A? ;显示字符 ’ A?
MOV AH,2
INT 21H
( 3)显示一个字符串功能号,09H。
入口参数,DS:DX=欲显示字符串在内存的首地址,且字符串必须以 ‘ $?(24H) 作为结束符。
出口参数:无。
功能:在当前光标位置,显示由 DS,DX所指的、以 ‘ $?
结尾的字符串,且光标右移。其中,‘ $?不算在显示的字符串之内。
( 4)从键盘读入一个字符串功能号,0AH。
入口参数,DS,DX=输入缓冲区首地址。
出口参数:无。
功能:从键盘读入一个字符串,存放 DS,DX所指的缓冲区。
下面介绍一下其他功能:
( 1) AH=25H:设置中断向量入口参数,AL=中断号。
DS,DX=中断向量。
功能:将 AL所指中断号的中断向量设置为 DS,DX。
( 2) AH=35H:获取中断向量入口参数,AL=中断号。
出口参数,ES,BX=中断向量。
功能:将 AL的指中断号的中断向量由 ES,BX返回。
( 3) AH=31H:程序终止并驻留内存入口参数,AL=返回码。
DX=驻留内存的节数( 1字 =16字节)。
功能:终止程序执行,全由 DX所确定的一部分代码工数据仍留在内存中。
2,BIOS服务
BIOS提供了访问主要 I/O设备的服务程序。
下面主要介绍显示器输出服务和键盘输入服务。
( 1) INT 10H:显示器输出
INT 10H包含了与显示器有关的功能,可用来设置显示方式,设置光标大小和位置,显示字符等。
① AH=0AH:显示字符入口参数,AL=欲显示字符的 ASCII码。
② AH=0EH:显示字符入口参数,AL=欲显示字符的 ASCII码。
功能:类似于 0AH功能,但显示字符后,光标随之移动,
并可解释回车,换行和退格等控制符。
( 2) INT=16H:键盘输入
① AH=0:从键盘读一键出口参数,AL=ASCII码,AH=扫描码。
功能:从键盘读入一个键后返回,按键不显示在屏幕上。
对于无相应 ASCII码的键,如功能键等,AL返回 0。
扫描码是表示按键所在位置的代码,有关细节将在以后讨论。
② AH=1:判断是否有键可读出口参数:若 ZF=0,则有键可读,AL=ASCII码,AH=
扫描码;否则,无键可读。
③ AH=2:返回变换键的当前状态出口参数,AL=变换键的状态。
[例 ]:读键盘输入,显示其中的 ASCII字符,按回车键退出,源程序如下。
CSEG SEGMENT PARA PUBLIC?CODE?
ASSUME CS,CSEG
START:
READNEXT,MOV AH,0
INT 21H
CMP AL,0DH
JZ READNEXT
MOV AH,0EH
INT 10H
JMP READNEXT
EXIT,MOV AH,4CH
INT 21H
CSEG ENDS
END START
习 题
1.假设 VAR1和 VAR2为字变量,LAB为标号,试指出下列指令的错误之处:
( 1) ADD VAR1,VAR2
( 2) SUB AL,VAR1
( 3) JMP LAB[SI]
( 4) JNZ VAR1
( 5) JMP NEAR LAB
2.画图说明下列语句所分配的存储空间及初始化的数据值。
( 1) BYTE_VAR DB?BYTE?,12,- 12H,3 DUP
( 0,?,2 DUP( 1,2),?)
( 2) WORD_VAR DW 5 DUP(0,1,2),?,- 5,
‘ BY?,‘ TE?,256H
3.假设程序中的数据定义如下:
PARTNO DW?
PNAME DB 16 DUP(?)
COUNT DD?
PLENTH EQU $—PARTNO
问 PLENTH的值为多少?它表示什么意义?
4.有符号定义语句如下:
BUF DB 1,2,3,‘ 123?
EBUFF DB 0
L EQU EBUFF—BUF
问 L的值为多少?
5,假设程序中的数据定义如下:
LMAME DB 30 DUP(?)
ADDRESS DB 30 DUP(?)
CITY DB 15 DUP(?)
CODE_LIST DB 1,7,8,3,2
( 1)用一条 MOV指令将 LNAME的偏移地址放入 AX。
( 2)用一条指令将 CODE_LIST的头两个字节的内容放入 SI。
( 3)用一条伪操作使 CODE_LENGTH的值等于
CODE_LIST域的实际长度。
6,试编写一个汇编语言程序,要求对键盘输入的小写字母用大写字母显示出来。
7.把三个连续存放的正整数,按递增次序重新存放在原来的三个存储单元中。
8,编写程序,从键盘接收一个小写字母,然后找出它的前导字符和后继字符,并按顺序输出这三个字符。
9.试编写一程序,比较两个字符串 STRING1和 STRING2
所含字符是否完全相同,若相同则显示,match”,若不同则显示,No match”。
10.试编写一个汇编语言程序,要求从键盘接收三个 16进制数,并根据对三个数的比较显示出如下信息:
( 1)如果三个数都不相等则显示 0;
( 2)如果三个数有两个数相等则显示 1;
( 3)如果三个数都相等,则显示 2。
11.编写程序,将一个包含 20个数据的数组 N分成两个数组:正整数组 P和负整数组 M,并分别把这两个数组中数据的个数显示出来。
12.试编写一个程序,要求从键盘上接收一个 4位的 16位进制数,并在终端上显示出与它等值的二进制数。
13.从键盘输入一系列以 $为结束符的字符串,然后对其中的非数字字符计数,并显示出计数结果。
14.数据区保存有 10个学生的姓名及其成绩,要求编写一程序将每个学生的成绩转换成 6个等级。 (A,90~ 100;
B,80~ 90; C,70~ 79; D,66~ 69; E,60~ 65; F:
60分以下 )。
15.编写一段程序,如果字节变量 TESTONE和
TESTTWO相等,则调用 ALLSAME子程序,否则调用
NOSAME子程序。
16,写一段子程序 SKIPLINE,完成输出空行的功能,空出的行数 AX寄存器中。
17.试编写一个子程序。使它能接收从键盘输入的十进制数(如益出则指示出错),并将它转换为二进制数存放在 DATA单元中。
18.编写一条宏指令 CLRB:完成用空格符将一字符区中的字符取代的工作。字符区首地址及其长度为变元。
19.定义宏指令 FINSUM:比较两个数 X和 Y,若 X>Y,
则执行 SUM←X+2 × Y,否则执行 SUM←2 × X+Y。
20,编写一段程序完成一段功能:
如变元 X=?VT55?,则汇编 MOV TERMINAL,0;否则汇编 MOV TERMINAL,1。