目录
(3)LOOPNZ / LOOPNE 当不为零/不相等时循环指令
第一章 汇编语言基础知识
一、机器语言与汇编语言
1、机器语言(第一代语言)
计算机硬件直接识别的程序设计语言,构成这种程序的是用二进制编码的机器指令,由计算机直接记忆、传输、识别和加工。这类语言复杂难记,还依赖于具体的机型,程序编写难度大,调试修改困难,无法在不同的机型间移植。
2、汇编语言(第二代语言)
一种面向机器的用符号表示的程序设计语言,也叫符号语言。与机器语言不同的是,汇编语言用直观、便于记忆和理解的英语单词或缩写符号来表示指令和数据变量。汇编指令也叫符号指令,符号称为助记符。汇编指令集和伪指令集及其使用规则的统称就是汇编语言。而计算机不认识助记符,因此通过一种叫汇编程序的翻译程序将汇编语言源程序翻译成机器码,才能提交计算机执行。这种对汇编语言源程序的翻译过程简称汇编。但汇编语言均对应一条机器语言,这与机器语言没有本质区别,仍是面向机器的低级语言。
特点:(1)移植性差,直接控制硬件;(2)程序效率高;(3)受指令限制;(4)调试困难。
3、高级语言(第三代语言)
用近乎自然语言或数学表达形式的程序设计语言,使程序设计工作避开与机器硬件相关,而着重于解决问题的算法本身,如Basic、C、Java等。用高级语言编写的源程序也必须经过编译和连接,将其转换为机器语言程序提交给计算机执行,或将其转换为一种中间代码,通过解释程序解释运行。
※ 无论用什么语言编程,最终在计算机硬件中执行的程序都是由机器码组成的,因此汇编语言是离机器语言最近的。
二、汇编语言的组成
1、汇编指令
机器码的助记符,有对应的机器码,是汇编语言的核心。
2、伪指令
没有对应的机器码,由编译器执行,计算机并不执行。
3、其他符号
如''+''、''-''、''*''、''/''等,由编译器识别,没有对应的机器码。
三、为什么要学习汇编语言
1、学习汇编语言对于从事计算机应用开发有重要作用
汇编语言程序是由符号指令写成的,本质上是机器语言,可直接有效地控制计算机硬件,运行速度快,程序短小精悍,占用内存容量少。在某些特定应用场合更能发挥作用。
2、学习汇编语言是从根本上认识和理解计算机工作过程的最好方法
通过汇编语言指令,可以清楚的看到程序在计算机中如何一步步执行,有利于更深入理解计算机的工作原理和特点。汇编语言把软件和硬件紧密地结合在一起,起到连接硬件和软件的桥梁作用。
第二章 计算机基本原理
一、计算机系统组成(冯·诺依曼结构)
1、中央处理器
CPU(Central Process Unit),或叫微处理器MPU(Micro Process Unit),主要包括运算器和控制器。运算器执行指令,控制器负责计算机的控制,负责从主存储器取指令,对指令进行译码,发出访问主存储器或I/O设备接口的控制信号,完成程序的要求。是计算机结构中最核心的部件,指令都是在这里执行的。
2、存储器
计算机记忆部件,以二进制形式存放程序和数据。主存储器简称主存,或叫内存储器,简称内存,记为RAM;硬盘、光盘等大容量存储器称为外部存储器,简称外存。
3、输入/输出子系统
包括大容量存储器(如硬盘)和其他外设,如显示器、键盘、打印机、鼠标等。
4、系统总线
连接CPU、主存储器和I/O子系统三大部分,用以完成各部分的数据交换,包括数据总线、地址总线和控制总线。数据总线负责传送数据,地址总线负责指示主存地址或I/O 接口地址,控制总线负责总线的动作,如时间、方向、状态。16位微处理器数据总线的位数为16位(bit),表示一次可以并行传输和处理16位二进制数据。32位微处理器数据总线的位数为32位(bit)。地址总线宽度的多少决定了可访问的内存容量的大小(CPU的寻址能力),数据总线的宽度的多少决定了数据传输的速度(CPU与其他器件进行数据传送时的一次数据传送量),控制总线宽度的大小决定了CPU对系统中其他器件的控制能力。
二、CPU中的寄存器
1、寄存器介绍
一个典型的CPU由运算器、控制器、寄存器等器件构成,寄存器相当于运算器中的高速存储单元,放置当前参与运算的操作数地址、数据、中间结果、处理机状态等。不同的CPU,其寄存器的个数、结构是不相同的。8086 CPU有14个寄存器,每个寄存器有一个名称。按照使用类别进行划分,80x86 CPU中的寄存器可以分为通用寄存器、段寄存器和专用寄存器三类。
(1)通用数据寄存器
8086 CPU的所有寄存器都是16位的,可以存放两个字节。AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据,有时候也可以存放地址,被称为通用数据寄存器。
① AX:累加器,运算时较多使用这个寄存器,有些指令规定必须使用它。
② BX:基址寄存器,除了存放数据,它经常用来存放一段内存的起始偏移地址。
③ CX:计数寄存器,除了存放数据,它经常用来存放重复操作的次数。
④ DX:数据寄存器,除了存放数据,它有时存放32位数据的高16位。
一个16位寄存器可以存储一个16位的数据。一个字存放在16位的寄存器中,这个字的高位字节和低位字节自然就存在这个寄存器的高8位和低8位中。8086 CPU的AX、BX、CX、DX这4个寄存器都可以分成两个可独立的8位寄存器来用,分别命名为:AH、AL、BH、BL、CH、CL、DH、DL。
例如,16位寄存器AX中存放数据为0000011111011000,所表示的十进制数值为2008;8位寄存器AH中存放数据为00000111,所表示的十进制数值为7;8位寄存器AL中存放数据为11011000,所表示的十进制数值为216。
(2)地址寄存器
16位的8086处理器有4个16位的通用地址寄存器。它们的主要作用是存放数据的所在偏移地址,也可以存放数据。这4个寄存器不能再拆分使用。
① SP:堆栈指针,这是一个专用的寄存器,存放堆栈栈顶的偏移地址。
② BP:基址指针,可以用来存放内存中数据的偏移地址。
③ SI:源变址寄存器,它经常用来存放内存中源数据区的偏移地址,所谓变址寄存器,是指在某些指令作用下它可以自动地递增或递减其中的值。
④ DI:目的变址寄存器,它经常用来存放内存中目的数据区的偏移地址,并在某些指令作用下可以自动地递增或递减其中的值。
(3)段寄存器
16位80x86处理器有4个16位的段寄存器,分别命名为CS,SS,DS,ES。它们用来存放4个段的段基址。
① CS:代码段寄存器,用来存放当前正在执行的程序段的段基址。
② SS:堆栈段寄存器,用来存放堆栈段的段基址。
③ DS:数据段寄存器,用来存放数据段的段基址。
④ ES:附加段寄存器,用来存放另一个数据段的段基址。
32位80x86处理器仍然使用16位的段寄存器,但是它们存储的内容发生了变化。此外,32位80x86处理器还增加了两个段寄存器FS和GS,它们的作用与ES类似。
(4)指令指针寄存器
IP:指令指针寄存器,存放即将执行指令的偏移地址。
(5)标志寄存器
FLAGS:存放CPU的两类标志。
状态标志:反映处理器当前的状态,如有无溢出、有无进位等。状态标志有6个:CF、PF、AF、ZF、SF和OF。
控制标志:用来控制CPU的工作方式,如是否响应可屏蔽中断等。控制标志有3个:TF、IF和DF。
标志位的情况是用两位符号进行描述的。
① OF:溢出标志。OF=1表示两个有符号数的运算结果超出了可以表示的范围,结果是错误的;OF=0表示没有溢出,结果正确。进行无符号数运算时也会产生新的OF标志(CPU 不知道处理对象是否为有符号数),此时程序员可以不关心OF标志。
② DF:方向标志。DF=0时,每次执行字符串指令后,源或目的地址指针用加法自动修改地址;DF=1时用减法来修改地址。它用来控制地址的变化方向。
③ IF:中断允许标志。IF=1表示允许处理器响应可屏蔽中断请求信号,称为开中断,IF=0表示不允许处理器响应可屏蔽中断请求信号,称为关中断。
④ SF:符号标志。SF=1表示运算结果的最高位为“1”。对于有符号数,在溢出标志 OF=0时,SF=1表示运算结果为负,SF=0表示运算结果非负(正或零)。OF=1时,由于结果是错误的,所以符号位也和正确值相反。例如,两个负数相加产生溢出,此时SF=0。对于无符号数运算,SF无意义(但是可以看出结果的大小规模)。
⑤ ZF:零标志。ZF=1表示运算结果为零,减法运算后结果为零意味着两个参加运算的数大小相等;ZF=0,表示运算结果非零。
⑥ AF:辅助进位标志。在进行字操作时,低字节向高字节进位时,AF=1,否则为0。一般用于两个BCD数运算后调整结果用,对其他数的运算没有意义。
⑦ PF:奇偶标志。PF=1表示运算结果的低8位中有偶数个“1”;PF=0表示有奇数个“1”。它可以用来进行奇偶校验。
⑧ CF:进位/借位标志。CF=1表示两个无符号数的加法运算有进位,或者是减法运算有借位,需要对它们的高位进行补充处理;CF=0表示没有产生进位或借位。同样,进行有符号数运算时也会产生新的CF标志,此时程序员可以不关心CF标志。
状态标志在每次运算后自动产生,控制标志的值则由指令设置。
※ 如何判断OF标志
① 加法运算时,若两数的最高位和次高位运算后进位情况不同,则OF=1,反之OF=0;
② 减法运算时,若被减数和减数的最高位不同,且差的最高位与被减数的最高位不同时,OF=1,反之OF=0;
2、CS和IP
8086 CPU的工作过程可以简要描述如下。
(1)从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
(2)IP=IP+所读取指令的长度,从而指向下一条指令;
(3)执行指令,转到步骤(1),重复这个过程。
CS和IP的内容提供了CPU要执行指令的地址。
CPU将CS:IP指向的内存单元中的内存看作指令,因为在任何时候,CPU将CS、IP中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,到内存中读取指令码,然后执行。如果说内存中的一段信息曾被CPU执行过的话,那么它所在的内存单元必然被CS:IP指向过。
3、堆栈
堆栈区的末单元称为栈底,数据先从栈底开始存放,最后存入的数据所在单元称为栈顶。当堆栈区为空时,栈顶和栈底是重合的。数据在堆栈区存放时,必须以字存入,每次存入一个字,后存入的数据依次放入栈的低地址单元中。栈指针SP每次减2,由栈指针SP指出当前栈顶的位置,数据存取时采用后进先出的方式。堆栈区常常用于保存调用的程序的返回地址以及现场参数,也可以作为一种临时的数据存储区。
第三章 汇编语言程序实例及上机操作
一、程序实例的上机步骤
一个汇编源程序从写出到最终执行,需要经过如下几个步骤。
(1)编写汇编源程序;
(2)对源程序进行汇编和连接;
(3)执行或调试可执行文件中的程序。
其中,第2步编译链接后出现错误,则会回到第1步。第3步执行时可以直接执行,也可以使用Debug环境进行调试和跟踪,了解程序的详细信息。
具体过程如下:
(1)用编辑程序EDIT建立ASM源文件;
(2)用汇编程序MASM将ASM源文件汇编为OBJ文件;
(3)用连接程序LINK将OBJ文件转换为EXE文件;
(4)在DOS环境下直接运行EXE文件,或在DEBUG环境下调试EXE文件。
1、编辑——建立ASM源程序文件
首先写源程序。编辑 ASM 源程序文件就是在机器上写程序,大多数文字编辑软件都可用来输入和修改汇编语言源程序。但要注意,源程序文件一定要储存为“纯文本”格式,源程序文件应该使用“ASM”作为扩展名。
下面介绍用EDIT编辑ASM源文件的步骤。
(1)进入DOS命令行方式。
(2)假定汇编语言的系统工作文件目录为D:\MASM6.15\,其中D:\表示D盘的根目录。可以通过以下命令指向D盘:
D: ↙
(3)如果屏幕显示不在此目录,可以通过以下命令进入该目录:
D:>CD \MASM6.15 ↙
当屏幕显示进入该目录后,用如下命令编辑源程序文件:
D: >MASM6.15>EDIT HELLO.ASM ↙
源程序文件须用“ASM”作为扩展名。最好存放在系统工作目录,便于下一步汇编。
2、汇编——产生OBJ二进制目标文件
汇编程序的作用是把汇编语言源程序翻译成为机器代码,产生二进制格式的目标文件(Object File)。
假定汇编语言源程序文件HELLO.ASM已经在当前目录D:\MASM6.15\下,用如下命令进行汇编:
D: >MASM6.15>MASM HELLO.ASM ↙ 或者 D:>MASM6.15>MASM HELLO ↙
该命令执行后,将产生一个同名的二进制目标文件HELLO.OBJ。如果源程序有语法错误,则不会产生目标文件,同时报错,提示源程序的出错位置和错误原因。
3、连接——产生EXE可执行文件
连接就是使用连接程序LINK把目标文件(OBJ)转换为可执行的EXE文件。
键入以下命令:
D: >MASM6.15>LINK HELLO.OBJ ↙ 或者 D: >MASM6.15>LINK HELLO ↙
如果文件HELLO.OBJ存在,机器有如下回应,要求用户对话,用户只需回车键认可默认值即可,这样就得到HELLO.EXE可执行的文件。
4、LST列表文件
如果希望在汇编的同时还得到一个列表文件,可以用如下命令进行汇编:
D: >MASM6.15>MASM HELLO HELLO HELLO↙
即给出3个HELLO作为命令参数,以空格分隔,该命令执行后,将产生MY.OBJ和列表文件MY.LST。即使源程序有语法错误,而得不到目标文件,也会产生列表文件HELLO.LST。
列表文件报告了汇编过程中产生的很多有价值的参考信息。主要包括源程序和机器语言清单、指令和变量的偏移地址等。列表文件是文本文件,可用 EDIT 调入。列表文件是可有可无的。
5、程序的运行
当汇编源程序编辑完成,经过汇编和连接后将生成*.EXE文件,即一个可以直接在操作系统下执行的程序文件。只需在命令行输入所需执行的文件名称,回车,即可得到运行结果。如执行HELLO.EXE,命令如下:
HELLO.EXE ↙ 或 HELLO ↙
程序文件必须在当前目录下。这里文件扩展名EXE可省略。真正的可执行文件是生成的,不是用改名操作得到的。
6、程序的跟踪和调试
程序执行的结果是否正确,以及如果出现错误,如何知道错在哪里,都必须经过调试阶段,才能观测结果和发现程序中的错误。调试程序Debug.EXE由Windows系统自带,使用方便。
在Debug下调试运行程序,并输入命令如下:
Debug HELLO.EXE ↙
用Debug调入HELLO.EXE,出现“-”Debug命令提示符。在“-”后可键入Debug命令。
(1)反汇编命令U
U命令把机器语言反汇编为汇编语言,便于用户看程序。命令格式为U[地址范围]。方括号表示可选。
(2)运行程序命令G
G命令用于执行程序,命令格式为G[=起始地址] [中止地址]。其中[中止地址]是为了给程序设置断点,让程序暂停在某个位置,便于观测。用G=0命令,即从偏移地址0000处执行程序。
(3)跟踪程序命令T
T命令用于单步执行程序,所以又叫跟踪程序。命令格式为T[=起始地址] [指令条数]。可以控制程序每执行一条指令就暂停,并显示当前机器情况。
这里需要特别指出:对于INT指令不能使用T命令跟踪。因为INT指令实质上是调用一个系统例行程序,T命令使程序进入了一个陌生的系统程序之中,当需要执行的单条指令为“INT 21H”时,则会出现陌生的系统程序。
(4)单步执行程序命令P
P命令用以执行循环、重复的字符串指令、软件中断或子例程。它克服了单步跟踪命令T的局限性,例如T命令无法一次执行的INT指令,P命令就可以一次执行完这个系统例行程序,回到用户程序中。
(5)退出命令Q
用Q命令退出Debug。
二、几个常用的DOS系统功能调用(INT 21H)
DOS系统功能INT 21H调用的方法:
(1)将调用功能的功能号存入AH寄存器;
(2)如必要,设置该调用功能的入口参数(调用参数);
(3)执行INT 21H指令;
(4)如必要,按规定取得出口参数(返回参数)。
注意:I/O处理操作的都是ASCII码,对于键盘输入的数字,做计算时需将ASCII码转为二进制数,输出显示时需要将二进制数转为ASCII码。
1、键盘输入单个字符并回显(1号功能)
例如:
MOV AH,1
INT 2lH
功能:等待从键盘输入一个字符,将该字符的ASCII码送入AL中,并送屏幕显示。
2、显示单个字符(2号功能)
例如:
MOV AH,2
MOV DL,'A'
INT 2lH
功能:在当前光标位置显示字符A。
注意:执行后AL寄存器的值被修改为DL的值。
3、显示字符串(9号功能)
例如:
MOV AH,9
LEA DX,STR
INT 2lH
功能:显示由DS:DX所指向的以“$”结束的字符串STR。
注意:执行后AL寄存器的值被修改为$的ASCII码24H。
4、键盘输入到缓冲区(0A号功能)
例如:
MOV AH,0AH
LEA DX,BUF
INT 2lH
功能:从键盘输入一串ASCII码到缓冲区,用“回车”结束输入。DS:DX=BUF首地址即缓冲区首地址,BUF缓冲区第1个字节是缓冲区大小(含回车键),可见键盘输入缓冲区最大为255个字节,是事先定义的。第2个字节是实际输入的字节数(不含回车键),当以回车键结束输入时系统自动存入的。从第3个字节开始存放输入的内容(含回车键)。数据段中的缓冲区应按上述规定的格式定义。
例题:键盘输入缓冲区程序:
DATA SEGMENT
BUF DB 9 ; 定义缓冲区大小为9个字节(含回车键)
REAL DB ? ; 实际输入的字符个数
STR DB 9 DUP(?) ; 输入的字符在这里(含回车键)
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
LEA DX, BUF ; 指向缓冲区
MOV AH, 0AH
INT 21H
MOV AH, 4CH
INT 2LH
CODE ENDS
END START
5、结束程序并返回DOS(4CH号功能)
例如:
MOV AH,4CH
INT 21H
功能:结束程序并返回操作系统。
第四章 操作数的寻址方式
立即寻址方式和寄存器寻址方式的指令在执行时,都无需到存储器寻找操作数(与存储器无关)。
直接寻址方式、寄存器间接寻址方式、寄存器相对寻址方式、基址变址寻址方式和相对基址变址寻址方式的操作数都在存储器中,要想得到操作数,CPU必须经过系统总线访问存储器,在指令执行阶段通过采用不同寻址方式求得操作数地址,才能取得操作数。由于存储器各个段的段地址已分别由各个段寄存器存放,因此,我们只需要根据操作数的偏移地址就可求出其物理地址。这里也可把偏移地址称为有效地址(Effective Address,简称EA)。
一、立即寻址方式
1、定义
所要找的操作数直接写在指令中,这种操作数称为立即数。立即数就在指令中(紧跟在操作码之后),这种寻址方式称为立即寻址方式。8086中立即数是8位或者16位。立即寻址方式用来表示常数,需要注意两个问题:
① 立即寻址方式只能用于源操作数字段;
② 立即数的类型必须与目的操作数的类型一致(长度也保持一致),目的操作数是字节,立即数也必须是字节,或者两者都是字。
2、用途
用于直接指定一个常数送给寄存器。立即寻址方式的操作数就在指令里,而指令本身在代码段中存放,当机器从内存取指令到CPU时,操作数作为指令的一部分一起取出来存入CPU的指令队列中。当CPU开始执行这条指令时,就可以立即得到操作数而无需再到内存去取。
3、例题
(1)MOV AL,6H
则指令执行以后,AL=06H指令中立即数6在机器中是8位而不是4位。
(2)MOV AX,12AFH
则指令执行以后,AX=12AFH,即AH=12H,AL=AFH,遵循高位数据在高地址的规定。
操作数在指令中也是遵循“双高”原则存放。
二、寄存器寻址方式
1、定义
操作数就是寄存器中的值,这种寻址方式称为寄存器寻址方式。指令中给出寄存器名。
对于16位操作数,寄存器可以是AX、BX、CX、DX、SI、DI、SP和BP等,对于8位操作数,寄存器可以是AH、AL、BH 、BL、CH、CL、DH、DL。
在寄存器寻址方式中,由于操作数在CPU内部的寄存器中,指令在执行时不需要访问内存,因而执行速度快,这一点和立即寻址相同,不同的是,立即数是指令的一部分,寄存器寻址方式中的操作数在CPU内部的寄存器中。
2、用途
用于指定2个寄存器作为操作数。
3、例题
MOV AX,BX
指令执行后,AX=BX,BX保持不变。
三、直接寻址方式
1、定义
操作数的有效地址EA就在指令中,这种寻址方式称为直接寻址方式。指令中直接给出了操作数的有效地址,当指令被机器取到CPU中并执行时,CPU就可以马上从指令中获取有效地址。
指令形式如下:
MOV AX,DS:[4050H]
操作数的有效地址EA直接写在指令中,用方括号里的数值作为操作数的偏移地址(有效地址)。操作数的段地址为数据段,由DS指出,即操作数本身存放在数据段中。CPU在取指令阶段可直接取得操作数EA,因而称为直接寻址方式。CPU根据EA和段地址DS计算出物理地址后,再访问存储器取出操作数的数值。
操作数的物理地址=(DS)*10H+EA
2、用途
用于直接指定一个变量作为操作数,适于处理单个变量。这里将存储单元看成变量,存储单元的名字(偏移地址)为变量名,存储单元的内容为变量值。
3、例题
(1)存储器读操作
MOV AX,DS:[2000H]
该指令表示从数据段的2000H单元读出一个字送入AX。
其中(DS)=1500H,(17000H)=31H,(17001H)=65H,(AX)=1020H
则有效地址EA=2000H,物理地址=(DS)*10H+EA=15000H+2000H=17000H
执行指令后:(AX)=6531H
(2)存储器写操作
MOV DS:[4000H],AX
将AX的值写入数据段的4000H单元。
已知(DS)=1500H,(AX)=3946H
则有效地址EA=4000H,物理地址=(DS)×10H+EA=15000H+4000H=19000H
执行指令后:(19000H)=46H,(19001H)=39H
(3)符号地址
直接寻址方式除了用数值作为有效地址之外,还可以用符号地址的形式。为存储单元定义一个名字,该名字就是符号地址。如果把存储单元看成变量,该名字也是变量名。
采用符号地址时,用数据定义伪指令DB、DW等定义的存储单元名字,其对应的段默认为数据段;但是如果用EQU符号定义伪操作来定义符号地址,则需要加上前缀“DS:”。
实际上,源程序中的变量总是用符号定义的,通常使用符号来表示操作数地址,可以方便程序员的编写和记忆,而不是用具体的数值表示。汇编语言源程序在汇编时,符号地址被转换为实际的偏移地址值。
VALUE DW 5678H
MOV AX,VALUE
MOV AX,[VALUE]
该指令表示从数据段的VALUE单元读出数据5678H送入AX。
有效地址EA=VALUE=1000H,物理地址=(DS)×10H+EA=15000H+1000H=16000H
若(16000H)=5678H,执行指令后:(AX)=5678H
(4)段前缀
在于内存有关的寻址方式中,操作数的段地址默认为数据段,80×86规定除了数据段之外,数据还可以存放在其他三种段中。如果操作数在其他段中存放,称为段超越,需要在指令中用段超越前缀指出,即用操作数前加上段寄存器名和冒号表示。
VALUE EQU 1000H
MOV AX,DS:[VALUE]
MOV AX,ES:[VALUE]
若已知(ES)=3600H,EA=VALUE=1000H
则段超越前缀ES的指令源操作数的物理地址计算方法为:物理地址=(ES)×10H+EA=36000H+1000H=37000H
若字单元(37000H)=9091H,执行完MOV AX,ES:[VALUE]后:(AX)=9091H
四、寄存器间接寻址方式
1、定义
操作数的有效地址EA就在寄存器中,这种寻址方式称为寄存器间接寻址。寄存器间接寻址方式与寄存器寻址方式不同,它不是把寄存器的内容作为操作数,而是把寄存器的内容作为操作数的地址,而操作数还是在内存中,故称为间接寻址。注意,在寄存器间接寻址中只允许 BX、BP、SI和DI。
操作数的物理地址=(DS)×10H+(BX)
操作数的物理地址=(DS)×10H+(SI)
操作数的物理地址=(DS)×10H+(DI)
操作数的物理地址=(SS)×10H+(BP)
由于EA是间接从寄存器中得到的,所以称为寄存器间接寻址方式。8086 CPU只允许BX、BP、SI、DI这四个寄存器作为间接地址寄存器。
2、用途
用寄存器间接指向一个内存单元,寄存器的值不同,指向的内存单元的地址就不同,常用于循环程序中,并适于简单的表格处理。
3、例题
(1)MOV AX,[BX]
已知(DS)=1500H,(BX)=4580H
则EA=(BX)=4580H,物理地址=(DS)×10H+EA=15000H+4580H=19580H
若(19580H)=2364H,执行指令后:(AX)=2364H
(2)MOV SS:[DI],AX
已知(SS)=2500H,(DI)=5318H
则EA=(DI)=5318H,物理地址=(SS)×10H+EA=25000H+5318H=2A318H
若(AX)=2468H,执行指令后:(2A318H)=68H,(2A319H)=24H
(3)MOV AX,[BX] ; 默认DS寄存器作段地址
MOV DX,[BP] ; 默认SS寄存器作段地址
MOV ES:[DI],AX ; 指定ES寄存器作段地址
注意,如果指令中指定BX、SI、DI寄存器作间接寻址的有效地址,则默认DS寄存器作段地址,如果指令中指定BP寄存器作间接寻址的有效地址,则默认SS寄存器作段地址。
五、寄存器相对寻址方式
1、定义
操作数的有效地址EA是一个寄存器和位移量之和,这种寻址方式称为寄存器相对寻址。和寄存器间接寻址方式不同的是,有效地址EA的构成除了寄存器以外,还要加上位移量。这里允许的寄存器和默认段寄存器的规定与寄存器间接寻址方式中一样,默认搭配也是DS 段寄存器和BX、SI、DI。SS段寄存器和BP。
操作数的物理地址=(DS)×10H+(BX)+8位(16)位位移量
操作数的物理地址=(DS)×10H+(SI)+8位(16)位位移量
操作数的物理地址=(DS)×10H+(DI)+8位(16)位位移量
操作数的物理地址=(SS)×10H+(BP)+8位(16)位位移量
由于有相对的位移量,所以称为寄存器相对寻址方式。可利用寄存器做首地址,用位移量做指针寻找表中特定的单元;或用位移量做表格的首地址,用寄存器做指针,来连续查表。
2、用途
此寻址方式常用于查表操作,特别适用于访问一维数组,寄存器可作为数组下标(或数组元素的位置),利用修改寄存器的值来定位数组中的各个元素。
3、例题
(1)MOV AX,TOP[SI]
以上指令TOP为符号地址,即位移量。
已知(DS)=1500H,(SI)=7310H,TOP=25H
则有效地址EA=(SI)+TOP=7310H+25H=7335H,物理地址=(DS)×10H+EA=15000H+7335H=1C335H
若(1C335H)=2428H,执行指令后,(AX)=2428H
(2)MOV[BX+2623H],AX 或 MOV[BX].2623H,AX
已知(DS)=1500H,(BX)=6854H
则有效地址EA=(BX)+2623H=8E77H,物理地址=(DS)×10H+EA=15000H+8E77H=1DE77H
若(1DE77H)=3567H,执行指令后:(AX)=3567H
(3)MOV AX,ARRY[BX]
MOV AX,[ARRY] [BX]
MOV AX,[ARRY+BX]
MOV AL,BUF[BX]
MOV AL,[BX+8H]
MOV AL,[BX].8H
前3条指令写法不同,但都是等效的。其中位移量 ARRY,通常是16位的变量,因为要和16位的寄存器匹配。
注意,这里源操作数的有效地址是由ARRY的偏移地址加上BX的值组成。
ARRY也可以是常量,第4条指令中的BUF通常是8位的变量,也可以是常量。
六、基址变址寻址方式
1、定义
操作数的有效地址是一个基址寄存器和一个变址寄存器的内容之和,这种寻址方式称为基址变址寻址。允许使用的基址寄存器为BX和BP,变址寄存器为SI和DI。默认段寄存器的规定与寄存器间接寻址方式中一样。
操作数的物理地址=(DS)×10H+(BX)+(DI)
操作数的物理地址=(DS)×10H+(BX)+(SI)
操作数的物理地址=(SS)×10H+(BP)+(SI)
操作数的物理地址=(SS)×10H+(BP)+(DI)
2、用途
可用于一维数组的处理,数组的首地址可放在基址寄存器,利用修改变址寄存器的内容来定位数组中的各元素。由于基址寄存器和变址寄存器都可以修改,所以访问数组中的各个元素更加灵活。
3、例题
(1)MOV AX,[BX+DI]
执行前:已知(DS)=2100H,(BX)=0158H,(DI)=10A5H,(221FD)=34H,(221FE)=95H,(AX)=0FFFFH
则有效地址EA=(BX)+(DI)=0158H+10A5H=11FDH,物理地址=(DS)×10H+EA=21000H+11FDH=221FDH
执行后,(AX)=9534H
MOV AX,[BX] [SI] ; 默认DS寄存器作段地址
MOV AX,[BP] [DI] ; 默认SS寄存器作段地址
MOV AX,ES:[BX] [DI] ; 指定ES寄存器作段地址
MOV DX,[BP] [SI] ; 默认SS寄存器作段地址
MOV [BX+DI],CX ; 默认DS寄存器作段地址
MOV [BP+SI],AL ; 默认SS寄存器作段地址
七、相对基址变址寻址方式
1、定义
操作数的有效地址是一个基址寄存器和一个变址寄存器以及一个位移量之和,这种寻址方式称为相对基址变址寻址。它所允许使用的基址寄存器为BX和BP,变址寄存器为SI和DI。默认段寄存器的规定与寄存器间接寻址方式中一样。位移量可以是常量,也可以是符号地址。
操作数的物理地址=(DS)×10H+(BX)+(DI)+8位(16位)位移量
操作数的物理地址=(DS)×10H+(BX)+(SI)+8位(16位)位移量
操作数的物理地址=(SS)×10H+(BP)+(SI)+8位(16位)位移量
操作数的物理地址=(SS)×10H+(BP)+(DI)+8位(16位)位移量
2、用途
可用于二维数组的处理,数组的首地址为ARRY,基址寄存器指向数组的行,变址寄存器指向该行的某个元素。利用修改基址寄存器和变址寄存器的内容可以方便地访问数组中的各个元素。
3、例题
MOV AX,MASK[BX] [SI] ; 默认DS寄存器作段地址
MOV AX,[MASK+BX+SI] ; 默认DS寄存器作段地址
MOV AX,[BX+SI].MASK ; 默认DS寄存器作段地址
以上3种表示形式实现的功能是一样的。其有效地址EA=MASK+(BX)+(SI);物理地址=(DS)×10H+EA。
第五章 常用指令系统
一、汇编语言指令的一般格式
格式:[标号:] 指令助记符 [操作数] [;注释]
例如:START: MOV AX,DATA ;DATA 送AX
1、标号
标号是一个符号地址,用来表示指令在内存中的位置,以便程序中的其他指令能引用该指令。它通常作为转移指令的操作数,以表示转向的目标地址。当一条指令使用标号时,应加冒号“:”,不可省略。
2、指令助记符
指令助记符表示指令名称,是指令功能的英文缩写。
3、操作数
操作数表示指令要操作的数据或数据所在的地址。操作数可以是寄存器、常量、变量,也可以由表达式构成。80x86指令一般带有0个、1个或2个操作数。对于双操作数指令,左边的操作数参与指令操作,存放操作结果,因此也称作目的操作数DST;而右边的操作数仅仅参与指令操作,指令运行结束后,内容不变,因此称作源操作数SRC。另外,2个操作数之间用逗号“,”分隔。
4、注释
注释由分号“;”开始,为了使程序更容易理解而用来对指令的功能加以说明。汇编程序对源程序汇编时,对注释部分不作处理。若注释超过一行则在每行都必须以分号开头。
二、数据传输指令
数据传送指令负责在寄存器、存储单元或I/O端口之间传送数据,是最简单、最常用的一类指令。
1、通用数据传送指令
(1)MOV 传送指令
格式:MOV DST,SRC
操作:(DST)←(SRC),将源操作数传送到目的操作数。
其中,DST表示目的操作数,SRC表示源操作数。
MOV指令为双操作数指令,须遵循双操作数指令的规定:
① 源操作数与目的操作数的长度必须明确且一致,即必须同时为8位或16位。
② 目的操作数与源操作数不能同为存储器,不允许在两个存储单元之间直接传送数据。
③ 目的操作数不能为CS或IP,因为CS:IP指向的是当前要执行的指令所在的地址。
④ 目的操作数不可以是立即数。
(2)MOV 例题:
① 立即数与寄存器的传送
MOV AH,89 ; 十进制数
MOV AX,2016H ; 十六进制数,后面加H
MOV AX,0ABCDH ; 十六进制数,因非数字(0~9)开头,前面加0
MOV AL,10001011B ; 二进制数,后面加B
MOV AL,'A' ; 字符’A’的ASCII码是41H,相当于立即数
说明:这5条指令中,源操作数均采用立即数寻址,但目的操作数是寄存器,其长度是明确的。只要立即数的长度不大于寄存器的位数即可。如MOV AH,258,这条指令是错误的,因为源操作数258表示成二进制超出8位,也就是比目的寄存器的位数长,不一致。
② 内存访问
MOV [BX],AX
上述指令的源操作数是寄存器AX,明确是16位的,主存单元[BX]可以表示指向一字单元,因此长度是一致的,指令合法。
但如MOV [BX],0这样的指令是错误的,因为上述指令的源操作数是立即数,其长度是不确定的,目的操作数是主存单元,但以低地址访问主存单元时,[BX]并不能说明是字节单元还是字单元,因此长度也是不确定的。
为了解决这个问题,可以在指令中指定内存单元的类型,将上述指令改写为下面两种形式,修改后的目的操作数长度就是明确的,指令是正确的。
MOV BYTE PTR[BX],0 ; BYTE PTR 说明是字节操作,写一个字节单元
MOV WORD PTR[BX],0 ; WORD PTR 说明是字操作,写一个字单元
③ 段地址寄存器的传送
MOV AX,DATA_SEG
MOV DS,AX
段地址寄存器须通过寄存器得到段地址,不能直接由符号地址、段寄存器、立即数得到。以下指令是错误的:
MOV DS,DATA_SEG ; 段寄存器不接受符号地址
MOV DS,ES ; 段寄存器之间不能直接传送
MOV DS,1234 ; 段寄存器不接受立即数
MOV CS,AX ; 指令合法,但代码段寄存器不能赋值
④ 传送变量
MOV BX, TABLE ; 假定TABLE是16位的变量
把变量TABLE的值送给BX。以下指令是错误的:
MOV BL,TABLE ; TABLE是16位的变量,操作数长度不一致
MOV [BX], TABLE ; 两个操作数不能同为内存单元
⑤ 传送地址
MOV BX, OFFSET TABLE
OFFSET 为偏移地址属性操作符,通常是把变量TABLE的偏移地址送给BX。以下指令是错误的:
MOV BL,OFFSET TABLE ; 不管变量的类型如何,其有效地址总是16位。
(3)PUSH 进栈指令
格式:PUSH SRC
操作:(SP)←(SP)-2
((SP)+1,(SP))←(SRC)
其中SRC表示源操作数。
该条指令表示将源操作数压入堆栈(目的操作数),目的操作数地址由SS:SP指定,指令中无需给出。堆栈是后进先出内存区,SP总是指向栈顶,即大地址。因此入栈时,先将栈顶指针SP减2(2表示2个字节,16位机器字长),以便指向新的内存地址接受16位源操作数,同时指向新的栈顶。堆栈操作以字为单位进行操作。
(4)POP 出栈指令
格式:POP DST
操作:(DST)←((SP)+1,(SP))
(SP)←(SP)+2
其中DST表示目的操作数。
将堆栈中源操作数弹出到目的操作数,堆栈中源操作数地址由SS:SP指定,指令中无需给出。源操作数弹出后,SP加2,下移一个字,指向新的栈顶。
(5)PUSH 与 POP 例题
① 进栈和出栈
MOV BX,1234H
PUSH BX
POP AX
进栈时SP-2,SP向低地址移动。出栈时SP+2,SP向高地址移动。
② 主存内容的堆栈操作
PUSH [2016] ; 把地址为DS:[2016]的字送往栈顶(SS:SP所指内存)
POP [2016] ; 把栈顶(SS:SP 所指内存)的字送往 DS:[2016]
这两条指令都为单操作数指令,但实际上另一个默认的操作数是栈顶,即SS:SP所指向的内存。而做的操作是从内存到内存的传送!这在双操作数MOV指令中是不允许的。
(6)XCHG 交换指令
格式:XCHG OPR1,OPR2
操作:(OPR1) ↔ (OPR2)
其中OPR1、OPR2为操作数。
把2个操作数互换位置。
XCHG为双操作数指令,两个操作数均是目的操作数,除了遵循双操作数指令的规定,也不能用立即数寻址。
(7)XCHG 例题
XCHG AX, BX ; 两个寄存器长度相等
XCHG AX, [BX] ; AX要求[BX]也取字单元
XCHG AX, VAR ; VAR 必须是字变量
以下指令是错误的:
XCHG AX, 5 ; 显然操作数不能为立即数
XCHG [BX], VAR ; 操作数不能同为内存单元
XCHG AX, BH ; 操作数长度要一致
2、累加器专用传送指令
输入/输出(I/O)端口是CPU与外设传送数据的接口,单独编址,不属于内存。端口地址范围为0000~FFFFH。这组指令只限于AX,AL(也称累加器)。
(1)IN 输入指令
把端口号PORT或由DX指向的端口的数据输入到累加器,根据端口号的长度,有长格式和短格式两种形式:
① 长格式:IN AL,PORT(字节)
IN AX,PORT(字)
操作:AL ← (PORT)
AX ← (PORT)
其中PORT为端口号,端口号范围为00~FFH时,可以使用长格式指令。所谓长格式指令,是指其机器指令长度为2个字节(端口号占1个字节)。
② 短格式:IN AL,DX(字节)
IN AX,DX(字)
操作:AL ← ((DX))
AX ← ((DX))
其中PORT为端口号,端口号范围为0100H~0FFFFH时,必须使用短格式指令。短格式指令长度为1个字节,因为端口号存放在DX寄存器中。
(2)IN 例题
① 读端口1
IN AX, 61H
MOV BX, AX
把端口61H的16位数据输入到累加器AX,再转送BX。
② 读端口2
MOV DX, 2F8H
IN AL, DX
把端口2F8H的8位数据输入到累加器AL。
以下指令是错误的:
IN AX, 2F8H ; 端口2F8H超出8位,不能用长格式
IN AX, [DX] ; 端口地址不能加方括号[]
(3)OUT 输出指令
把累加器的数据输出到端口PORT或由DX指向的端口。与输入指令相同,根据端口号的长度,分为长格式和短格式两种形式:
① 长格式:OUT PORT,AL(字节)
OUT PORT,AX(字节)
操作:PORT ← AL
PORT ← AX
② 短格式:OUT DX,AL(字节)
OUT DX,AX(字节)
操作:(DX) ← AL
(DX) ← AX
(4)OUT 例题
OUT 61H, AL ; 把累加器的数据输出到端口61H
OUT DX, AL ; 把累加器的数据输出到DX指向的端口
(5)XLAT 换码指令
格式:XLAT
操作:AL ←(BX+AL)
把BX+AL的值作为有效地址,取出其中的一个字节送AL。
(6)XLAT 例题
DATA SEGMENT
ORG 0100H
STRING DB 'ABCDEFG'
DATA ENDS
CODE SEGMENT
MAIN PROC FAR
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
MOV BX, 100H
MOV AL, 4
XLAT
INT 21H
MOV AH, 4CH
INT 21H
MAIN ENDP
CODE ENDS
END START
3、地址传送指令
(1)LEA 有效地址送寄存器指令
格式:LEA REG,SRC
操作:REG ← SRC
把源操作数的有效地址EA送到指定的寄存器。
(2)LEA 例题
① 取变量的有效地址1
LEA BX,TABLE
MOV BX,OFFSET TABLE
上面2条指令是等效的。TABLE无论是何种类型的变量,其有效地址总是16位。
② 取变量的有效地址2
LEA BX,[2016H]
MOV BX, OFFSET[2016H]
指令执行后,BX=2016H。
(3)LDS 指针送寄存器和DS指令
格式:LDS REG,SRC
操作:REG ← (SRC)
DS ← (SRC+2)
把源操作数SRC所指向的内存单元中2个字送到指定的寄存器REG和DS。
(4)LDS 例题
LDS SI, [BX]
指令执行前,DS=2000H,BX=0400H,(2000:0400)=1234H,(2000:0402)=5678H
指令执行后,SI=1234H,DS=5678H。
(5)LES 指针送寄存器和DS指令
格式:LDS REG,SRC
操作:REG ←(SRC)
ES ←(SRC+2)
把源操作数SRC所指向的内存单元中2个字送到指定的寄存器REG和ES。
4、标志寄存器传送
◆ LAHF(Load AH with Flags) 标志送AH寄存器;
◆ SAHF(Store AH into Flags) AH送标志寄存器;
◆ PUSHF(Push Flags) 标志入栈;
◆ POPF(Pop Flags) 标志出栈;
指令的格式相同,只有操作码部分,操作数为固定默认值,且传送类指令(除SAHF、POPF外)均不影响标志位。
LAHF ; 标志寄存器低字节送AH寄存器
SAHF ; AH送标志寄存器
PUSHF ; 标志入栈
POPF ; 标志出栈
三、算术运算指令
1、类型扩展指令
当指令需要双操作数时,通常要求两操作数的长度一致。为解决操作数长度的一致问题,有时需要将某操作数的数据类型进行扩展。
◆ CBW 字节扩展成字:convert byte to word
◆ CWD 字扩展成双字:convert word to double word
这两条指令的格式相同,只有操作码部分,无操作数部分。操作数默认为累加器,无需在指令中给出。
(1)CBW 字节扩展成字
执行CBW时,默认将AL寄存器的内容扩展到AX寄存器中,扩展方法为符号扩展,即如果AL的最高位为1(负数),则CBW指令扩展时使AH=FFH,如果AL的最高位为0(正数),则CBW指令扩展时使AH=00H。
(2)CWD 字扩展成双字
执行CWD时,默认将AX寄存器的内容扩展到(DX,AX)中,其中DX存放双字中的高位,AX存放双字中的低位。如果AX的最高位为1(负数),则 CWD 指令扩展时使 DX=FFFFH,如果 AX 的最高位为0(正数),则 CWD 指令扩展时使DX=0000H。
(3)CBW 和 CWD 例题
① 正数的扩展
MOV AH,11H ; AH赋值为11H
MOV DX,1111H ; DX赋值为1111H
MOV AL,52H ; AL中的52H是正数
CBW ; 指令执行后,AX=0052H
CWD ; 指令执行后,DX=0000H,AX=0052H
② 负数的扩展
MOV AH,11H ; AH赋值为11H
MOV DX,1111H ; DX赋值为1111H
MOV AL,88H ; AL中的88H是负数
CBW ; 指令执行后,AX=FF88H
CWD ; 指令执行后,DX=FFFFH,AX=FF88H
2、加法指令
(1)ADD 加法指令
格式:ADD DST,SRC
操作:(DST)←(DST)+(SRC)
ADD指令将源操作数与目的操作数相加,结果存入目的操作数中。
特别需要注意,加法指令执行后会影响标志寄存器中的CF和OF标志位。
(2)ADD 例题
① 无符号数的溢出标志位CF
MOV AL,72H
ADD AL,93H
(i)当93H和72H被视为无符号数时,该指令在机器中执行后,AL寄存器中的内容为05H,而不是我们希望的结果93H+72H=105H。因为AL寄存器只能存放8位二进制,而105H需要至少9位二进制才可以表示,显然AL寄存器不可能存放入105H,而只是存放入了低八位的数值,即05H。而最高位的进位1进到标志寄存器中的CF位了,这种情况叫作产生进位,也叫无符号数的溢出。
(ii)当93H和72H被视为有符号数时,则这两个数据都是补码形式,其十进制原码转换可得:
72H=01110010B=114D=[114D]补
93H=10010011B=[-109D]补
则上述加法运算等同于十进制数114和(-109)的加法,显然这个结果AL寄存器的内容等于05H是正确的。
因此,在进行加法运算时。如果是无符号数,则加法运算的结果是否正确,参看标志寄存器中的CF位,如果CF=0,表明结果是正确的。如果CF=1,表明结果是错误的。如果参与运算的数据是有符号数,则不需考虑CF的结果。
② 有符号数的溢出标志位OF
MOV AL,92H
ADD AL,93H
当92H和92H均是有符号数时,则其十进制原码转换可得:
92H=10010010B=[-110D]补
93H=10010011B=[-109D]补
则上述加法运算等同于十进制数(-110)和(-109)的加法,其运算结果应该是(-219),但实际运算后AL寄存器的内容为25H。因为AL寄存器只有8位,其能表示的最小负数为-128,所以结果肯定是溢出了。此时机器对标志寄存器中的 OF 位置1,表示有符号数的补码加法结果出现溢出。因此可以根据OF位来判断有符号数的补码加法结果是否正确。
③ 无符号数和有符号数运算后的结果以及进位与溢出的情况
8位二进制数可以表示的十进制数的范围是:无符号数为0~255,有符号数为-128~+127。
(i)无符号数和有符号数都正常
(ii)无符号数有溢出
(iii)有符号数有溢出
(iv)无符号数和有符号数都溢出
不管看作无符号数的加法还是有符号数的补码加法,计算机只是做二进制加法,结果是唯一的,但可能是错误的结果,即存在溢出现象。当无符号数发生溢出时,CF标志位置1;当有符号数发生溢出时,OF标志位置1。
当参与运算的数为无符号数时,其最高位的进位将进位给CF标志位,此时CF标志位为1,反映了无符号数有溢出。
当参与运算的有符号数一个是正数,一个是负数,其结果无论是正数或负数,都不会发生溢出。
当两个有符号数的符号相同时则有可能发生溢出,此时需要根据 OF 标志位判断有符号数的加法结果是否溢出。
(3)ADC 带进位加法指令
格式:ADD DST,SRC
操作:(DST)←(DST)+(SRC)+CF
其中上式中的CF为运算前CF标志位的值。
(4)ADC 例题
用16位指令实现32位的双精度数的加法运算
设数A存放在目的操作数寄存器DX和AX,其中DX存放高位字。数B存放在寄存器BX和CX,其中BX存放高位字。如:
DX=2000H,AX=8000H,BX=4000H,CX=9000H
则指令序列为:
ADD AX, CX ; 低位字加
ADC DX, BX ; 高位字加
① 第一条指令执行后,AX=1000H,CF=1,OF=1,因为这条指令运算的是数据的低位,所以不必考虑溢出问题,但CF=1表示运算有进位,此进位应该是合法的进位。
② 第二条指令执行时采用ADC指令,相比ADD指令,将CF标志位的值代入加法中,充分考虑了低位的进位,保证了运算结果的正确性。最终DX=6001H,CF=0,OF=0,表示结果正常,无溢出。
因此为实现双精度加法,必须用两条指令分别完成低位字和高位字的加,并在高位字相加时使用 ADC 指令,从而把低位字相加时产生的进位值加进来。这时,不论是无符号数还是有符号数,低位字相加时无需考虑溢出,只有在高位字相加时所产生的CF位和OF位才是判断是否溢出的依据。
(5)INC 加1指令
格式:INC OPR
操作:(OPR)←(OPR)+1
该指令不影响CF标志位。
3、减法指令
减法运算的标志位情况与加法类似,CF标志位指明无符号数的溢出,OF标志位指明有符号数溢出。
(1)SUB 减法指令
格式:SUB DST,SRC
操作:(DST)←(DST)-(SRC)
(2)SBB 带借位减法指令
格式:SBB DST,SRC
操作:(DST)←(DST)-(SRC)-CF
(3)DEC 减1指令
格式:DEC OPR
操作:(OPR)←(OPR)-1
该指令不影响CF标志位。
(4)NEG 求补指令(求相反数)
格式:NEG OPR
操作:(OPR) ← -(OPR)
(5)CMP 比较指令
格式:CMP OPR1,OPR2
操作:(OPR1)-(OPR2)
(6)减法指令例题
① 考察减法中的CF、OF标志位
MOV AL, 72H
SUB AL, 93H
指令执行后,AL寄存器的内容为DFH。
(i)当将72H和93H视为无符号数时,被减数72H比减数93H小,不够减,这个结果显然应该是错误的。在实际运算时,计算机允许被减数向高位借位,因为不存在实际的高位,所以就体现在CF标志位。当减法运算中需要借位时,将CF标志位置1,说明无符号数运算的溢出。则上式相当于运算172H-93H=DFH。
(ii)当将72H和93H视为有符号数时,被减数72H是正数(符号位为0),减数93H是负数(符号位为1),正数减去负数,其结果应该为正数。而本题运算结果为DFH(符号位为1),是一个负数,此时机器对标志位OF置1,说明有符号数运算的溢出。93H是(-109D)的补码,72H是(114D)的补码,72H-93H=(14D)-(109D)=223D,超出了8位有符号数的表示范围,因此确实会发生溢出。
当参与运算的两个有符号数的符号相同时,其结果无论是正数或负数,都不会发生溢出。
当两个有符号数的符号相反时则有可能发生溢出,此时需要根据 OF 标志位判断有符号数的减法结果是否溢出。
② 用16位指令实现32位的双精度数的减法运算
设数A存放在目的操作数寄存器DX和AX,其中DX存放高位字。数B存放在寄存器BX和CX,其中BX存放高位字。如:
DX=2001H,AX=8000H,BX=2000H,CX=9000H
指令序列为:
SUB AX,CX ; 低位字减法
SBB DX,BX ; 高位字减法
第一条指令执行后,AX=F000H,CF=1,而对OF=0,ZF=0,SF=1,不必在意。
第二条指令执行后,DX=0000H,CF=0,OF=0,表示结果正确。ZF=1,SF=0。
因此为实现双精度减法,必须用两条指令分别完成低位字和高位字的减法,并在高位字作减法时使用SBB指令,从而把低位字减法时产生的借位加进来。这时,不论是无符号数还是有符号数,低位字作减法时无需考虑溢出,只有在高位字作减法时所产生的CF位和OF位才是判断是否溢出的依据。
③ 考察NEG指令
MOV AX, 3
NEG AX
MOV DX, 0
NEG DX
NEG指令表示求数X的相反数,实际上是求解0-X,只有当X=0时,CF=0,其他情况下CF=1。
④ 考察CMP指令
MOV AX, 5
DEC AX
CMP AX, 5
指令序列执行后,AX=4,ZF=0,SF=1,CF=1,OF=0。
CMP指令虽作减法,但不回送结果,只是产生标志位,为比较两个数的大小提供判断依据。
以上加减法指令都可作字或字节运算,除了INC、DEC指令不影响CF标志外,其他指令都影响条件标志位。
4、乘法指令
在乘法指令中,目的操作数默认为累加器 AX,不必在指令中写出。因为两个相乘的数必须长度相同,根据SRC的长度,默认参与运算的是AL寄存器的值(即为AX寄存器的第八位)或者是AX寄存器的值。SRC可以是寄存器或变量,但不能是立即数,因为立即数的长度是不明确的。
运算结果即乘积的长度默认是乘数的两倍,不会出现溢出情况。和加减法指令不同的是,在作乘法运算时需根据参与运算的是无符号数还是有符号数,选择不同的指令。
(1)MUL 无符号数乘法指令
格式:MUL SRC
操作:
当操作数为字节时,(AX)←(AL)×(SRC)
当操作数为字时,(DX,AX)←(AX)×(SRC)
(2)IMUL 有符号数乘法指令
格式和操作与MUL相同,用来作有符号数乘法。
(3)MUL 和 IMUL 例题
无符号数和有符号数的乘法
MOV AL, F1H
MOV BL, AL
MUL BL
IMUL BL
当作为两个无符号数相乘,指令序列执行后,AX=E2E1H。
当作为两个有符号数相乘,指令序列执行后,AX=00E1H。表明了两个负数相乘,结果为正数。
显然这两条指令在机器中的操作有很大的不同,IMUL指令会考虑符号规则。
5、除法指令
在除法指令里,目的操作数必须是累加器 AX 和 DX,不必在指令中写出。被除数长度应为除数长度的两倍,余数放在目的操作数的高位,商放在目的操作数的低位。其中SRC不能是立即数。另外,和作乘法时相同,作除法时需考虑是无符号数还是有符号数,从而选择不同的指令。
由于除法指令的字节操作要求被除数为16位,字操作要求被除数为32位,因此往往需要用符号扩展指令使得被除数长度比除数长度扩大一倍。
需要注意的是,在进行乘法运算时,不会发生溢出问题,但在使用除法指令时,会产生溢出现象。
当除数是字节类型时,除法指令要求商为8位。此时如果被除数的高8位绝对值≥除数的绝对值,则商会产生溢出。
当除数是字类型时,除法指令要求商为16位。此时如果被除数的高16位绝对值≥除数的绝对值,则商会产生溢出。
(1)DIV 无符号数除法指令
格式:DIV SRC
操作:
SRC为字节时,(AL)←(AX)/(SRC)的商,(AH)←(AX)/(SRC)的余数。
SRC为字时,(AX)←(DX,AX)/(SRC)的商,(DX)←(DX,AX)/(SRC)的余数。
该指令将参与运算的数据默认为无符号数,则商和余数都是无符号数。
(2)IDIV 有符号数除法指令
指令格式和操作与无符号数除法相同,用作有符号数除法。最终商的符号应是两个操作数符号的异或,余数符号和被除数符号一致。
(3)DIV 和 IDIV 例题
① 作(字节)除法300H/2H,商产生溢出
MOV AX, 300H
MOV BL, 2
DIV BL
此时被除数的高8位(AH=3)绝对值>除数的绝对值2,则商会产生溢出。
实际上换成十进制计算也可说明商会产生溢出:300H/2H=768/2=384,显然,8位的AL寄存器存不下商384。
其实,只要把被除数和除数的长度扩大一倍就可避免商溢出。
② 作(字)除法300H/2H,商不会产生溢出
MOV AX, 300H
CWD
MOV BX, 2
DIV BX
此时被除数的高16位为0,即DX=0,则商不会产生溢出。显然AX寄存器完全能容下商384。
6、算术运算综合
计算:(V-(X×Y+Z-16))/X
其中X、Y、Z、V均为16位有符号数,在数据段定义,要求上式计算结果的商存入AX,余数存入DX寄存器。编制程序如下:
DATA SEGMENT
X DW 4
Y DW 2
Z DW 14H
V DW 18H
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX, DATA
MOV DS, AX
MOV AX, X
IMUL Y ; X×Y
MOV CX, AX ; 暂存(X×Y)的结果
MOV BX, DX
MOV AX, Z
CWD ; Z符号扩展
ADD CX, AX ; 加Z
ADC BX, DX
SUB CX, 16 ; 减16
SBB BX, 0
MOV AX, V
CWD ; V符号扩展
SUB AX, CX ; V减(X×Y+Z-16)的结果
SBB DX, BX
IDIV X
MOV AH, 4CH
INT 21H
CODE ENDS
END START
经过编辑、汇编、连接生成可执行文件后,用Debug调入程序,程序所在范围在偏移地址为0~2C之间。
对程序进行调试,先用T命令执行指令,使DS赋值为076A。再用D0 L 8命令可显示位于DS:0000处的8个字节单元,其值是4个16位的数据,即X,Y,Z,V。用G=0 2A命令,从代码段的偏移地址0开始执行程序,停于偏移地址2A处。尽管程序没有全部结束,但运算结果已经得到AX=0003和DX=0000就是结果。
7、BCD码的十进制调整指令
二—十进制(Binary Code Decimal,BCD码)是4位二进制数编码表示一位十进制数的编码。由于这4位二进制数的位权分别为8、4、2、1,所以BCD码又称8421码。具体的编码如下表所示。这种BCD码又叫压缩的BCD码,这是计算机中常用的。
(1)DAA(加法的十进制调整指令)
格式:DAA
操作:加法指令中,以AL为目的操作数,当加法运算结束后,使用本指令可以把AL中的和调整为正确的BCD码格式。即:
① 如果AL低4位>9,或AF=1,则AL=AL+6;
② 如果AL高4位>9,或CF=1,则AL=AL+60H,CF=1。
(2)DAA 例题
① AL=28H=28(BCD),BL=65H=65(BCD)
ADD AL, BL ; AL=28H+65H=8DH
DAA ; AL=AL+6H=8DH+6H=93H=93(BCD)
AL和BL中都是用BCD码表示的十进制数,含义分别是28和65,ADD指令作二进制加法后得到8DH,不是BCD码,DAA指令作用后,把和调整为93H,但它表示的是十进制数93的BCD码。
② AX=88H=88(BCD),BX=89H=89(BCD)
ADD AL, BL ; AL=88H+89H=11H,AF=1,CF=1
DAA ; AL=AL+66H=11H+66H=77H=77(BCD),CF=1
ADC AH, 0 ; AX=177H=177(BCD)
第一条加法指令中的低四位加产生了向高四位的进位,这使得辅助进位 AF 置1,高四位加产生的进位使得进位CF置1,因此使用DAA指令后,根据AF的值需要加6H,根据CF的值需要加60H,因此将AL的内容加上66H,把和调整为77H,CF=1,最后ADC指令使AX中得到177H,即十进制数177的BCD码。
(3)DAS(减法的十进制调整指令)
格式:DAS
操作:减法指令中,以AL为目的操作数,当减法运算结束后,使用本指令可以把差调整为BCD码格式。即:
① 如果AL低4位>9,或AF=1,则AL=AL-6,AF=1;
② 如果AL高4位>9,或CF=1,则AL=AL-60H,CF=1。
(4)DAS 例题
AL=93H=93(BCD),BL=65H=65(BCD)
SUB AL, BL ; AL=93H-65H=2EH
DAS ; AL=AL-6H=2EH-6H=28H=28(BCD)
四、逻辑与移位指令
1、逻辑指令
逻辑运算指令只会对部分标志位产生影响,其中 NOT 指令不影响任何标志位,其他指令将使CF位和OF位为0,AF位无定义,其他位则根据运算结果设置。
逻辑指令除了常规的逻辑运算功能外,通常还可以用来对操作数的某些位进行处理,例如屏蔽某些位(将这些位置O),或使某些位置1,或测试某些位等。
(1)AND(与指令)
格式:AND DST,SRC
操作:(DST)←(DST)∧(SRC)
(2)OR(或指令)
格式:OR DST,SRC
操作:(DST)←(DST)∨(SRC)
(3)NOT(非指令)
格式:NOT OPR
操作:(OPR)←(OPR)
(4)XOR(异或指令)
格式:XOR DST,SRC
操作:(DST)←(DST)∀(SRC)
(5)TEST(测试指令)
格式:TEST OPR1,OPR2
操作:(OPR1)∧(OPR2)
说明:TEST指令的两个操作数相与的结果不保存,只根据结果置标志位。
(6)逻辑指令例题
① 屏蔽AL寄存器的高四位,目前AL=36H
AND AL, 0FH
指令执行的结果为AL=06H,实现了高四位的屏蔽。
② 将AL寄存器的最低两位置1,目前AL=36H
OR AL, 03H
指令执行的结果为AL=37H,即最低两位实现了置1。
③ 对AL寄存器的最低两位取反,目前AL=36H
XOR AL, 03H
指令执行的结果为AL=35H,实现了对最低两位的取反。
④ 测试AL寄存器中的数,如果是负数则转到标号NEXT去执行。如AL=86H
TEST AL, 80H
JS NEXT
指令执行的结果AL=86H(不变),但TEST指令的结果将标志寄存器的SF位置1,因此JS跳转指令会跳转到标号NEXT去继续执行。
2、移位指令
(1)移位指令说明
◆ SHL(Shift Logical Left) 逻辑左移
◆ SAL(Shift Arithmetic Left) 算术左移
◆ SHR(Shift Logical Right) 逻辑右移
◆ SAR(Shift Arithmetic Right) 算术右移
◆ ROL(Rotat Left) 循环左移
◆ ROR(Rotat Right) 循环右移
◆ RCL(Rotat Left with Carry) 带进位循环左移
◆ RCR(Rotat Right with Carry) 带进位循环右移
移位指令均是双操作数指令,指令的格式相同,以SHL为例,则:
格式:① SHL OPR, 1
② SHL OPR, CL ; 其中CL寄存器的值大于1
其中OPR为寄存器或内存单元,移位次数可以是1或CL寄存器,如需移位的次数大于1,则可以在该移位指令前把移位次数先送CL寄存器中。
当执行逻辑或算术左移时,操作结果相同,均是最低位补0,移出的最高位送CF标志位;
当执行逻辑右移时,最高位补0,移出的最低位送CF标志位;
当执行算术右移时,OPR被认为是有符号数,则最高位补符号位自身,移出的最低位送CF标志位;
当执行循环左移时,OPR整体向左移一位,最高位移出,同时送CF标志位和最低位;
当执行循环右移时,OPR整体向右移一位,最低位移出,同时送CF标志位和最高位;
当执行带进位循环左移时,OPR整体向左移一位,此时最高位移出送CF标志位,而CF标志位原始的数值送OPR最低位;
当执行带进位循环右移时,OPR整体向右移一位,此时最低位移出送CF标志位,而CF标志位原始的数值送OPR最高位。
(2)移位指令例题
指令执行前,AX=13H=00010011B,CF=1。
① SHL AX, 1
SHL:00010011整体左移,最高位0送入 CF 标志位,最低位补0,结果为AX=00100110B=26H,CF=0,相当于对AX的内容乘以2。
② SHR AX, 1
SHR:00010011整体右移,最低位1送入CF标志位,最高位补0,结果为00001001=09H,CF=1,相当于对AX的内容除以2;
若AX=10010011B,则SHR执行的结果为01001001=49H,CF=1。
③ SAR AX, 1
SAR:00010011整体右移,最低位1送入CF标志位,最高位补符号位本身,即0,结果为00001001=09H;
若AX=10010011B,则SAR执行的结果与SHR不同,结果为11001001=C9H,CF=1。
④ ROL AX, 1
ROL :00010011整体左移,最高位0同时送入 CF 标志位和最低位,结果为AX=00100110B=26H,CF=0。
⑤ ROR AX, 1
ROR :00010011整体右移,最低位1同时送入 CF 标志位和最高位,结果为AX=10001001B=89H,CF=1。
⑥ RCL AX, 1
RCL:00010011整体左移,最高位0送入CF标志位,而CF标志位原始的值(CF=1)送入最低位,结果为AX=00100111B=27H,CF=0。
⑦ RCR AX, 1
RCR:00010011整体右移,最低位1送入CF标志位,而CF标志位原始的值(CF=1)送入最高位,结果为AX=10001001B=89H,CF=1;
若 CF 标志位原始的值 CF=0,则结果为AX=00001001B=09H,CF=1。
⑧ 对AX中内容实现半字交换,即交换AH和AL中的内容
MOV CL, 8
ROL AX, CL
若指令执行前,AX=1234H,则指令执行后,AX=3412H。
五、串操作指令
串操作指令用以处理内存中的数据串,但该操作每一次执行处理的只是单个字节或字,因此对于数据串来说,需要重复执行串操作指令才能处理完整个串。串操作指令的重复有特定的前缀指令配合,其中包含前缀指令:
◆ REP(repeat) 重复
◆ REPE/REPZ(repeat while equal/zero) 相等/为零则重复
◆ REPNE/REPNZ(repeat while not equal/not zero) 不相等/不为零则重复
(1)前缀REP的作用是重复执行串操作指令,直到寄存器CX=0为止,而每执行一次串操作指令,会使CX的内容自动减1,因此总的重复次数等于CX寄存器的初始值。
(2)前缀REPE也叫REPZ,只有当CX寄存器的值≠0并且标志位ZF=1时,重复执行串操作指令。若用以比较两个字符串是否相等,每次的串操作指令把源串中的一个字节和目的串中的一个字节进行比较,如果相等(即ZF=1),则还需继续执行串操作指令,若不相等或者比较全部串的数据(CX=0),则停止。
(3)前缀REPNE也叫REPNZ,只有当CX寄存器的值≠0并且标志位ZF=0时,重复执行串操作指令。若在一个字符串中查找是否存在某一个字符,串操作指令把字符串中的一个字节和要找的这个字符进行比较,如果不相等(即ZF=0),则还需继续执行串操作指令,直到找到(ZF=1)或者查找完整个串的数据(CX=0),才停止。
1、MOVS 串传送指令
(1)字节(MOVSB)操作
(ES:DI)←(DS:SI),DI±1,SI±1
(2)字(MOVSW)操作
(ES:DI)←(DS:SI),DI±2,SI±2
上述操作中,当方向标志DF=0时,SI、DI用+;当DF=1时,SI、DI用-。方向标志DF的设置有两条指令:
◆ CLD(clear direction flag)设置正向(向前,使DF=0,SI或DI自动加)
◆ STD(set direction flag)设置反向(向后,使DF=1,SI或DI自动减)
上述传送指令默认源操作数是DS和源变址寄存器SI构成的地址,ES和目的变址寄存器 DI 构成的地址为目的操作数地址,并且通过方向标志指令设置自动变址功能,数据的传送是从内存到内存的传送。
因此,为了实现整个串的传送,在使用串操作指令前,应该做好如下准备工作:
① 数据段中的源串首地址(如反向传送则是末地址)送DS和SI;
② 附加段中的目的串首地址(如反向传送则是末地址)送ES和DI;
③ 串长度送计数寄存器CX;
④ 方向标志DF。
(3)MOVS 例题
在数据段中有一个字符串 MESS,其长度为19,要求把它们转送到附加段中名为BUFF的一个缓冲区中,并显示出BUFF字符串,编制程序如下所示:
DATA SEGMENT
MESS DB 'COMPUTER SOFTWARE $ '
DATA ENDS
EXT SEGMENT
BUFF DB 19 DUP(?)
EXT ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,ES: EXT
START:
MOV AX,DATA ; 赋段地址
MOV DS,AX
MOV AX,EXT
MOV ES,AX
LEA SI,MESS ; 赋偏移地址
LEA DI,BUFF
MOV CX,19 ; 串长
CLD ; 设置DF的方向
REP MOVSB ; 完成串传送
MOV BX,ES ; 准备显示BUFF字符串
MOV DS,BX ; DS:DX指向待显示串的地址
LEA DX,BUFF
MOV AH,9
INT 21H
MOV AH,4CH
INT 21H
CODE ENDS
END START
程序中定义了数据段DATA,其中用DB伪操作定义了字节类型数据串MESS,串MESS中的每个字符占一个字节,以该字符的ASCII码存放。定义了附加段EXT,其中定义的串BUFF的长度由DUP操作符开辟了19个字节空间,没有赋值。
(4)MOVS 串传送指令
格式:MOVS ES:BYTE PTR[DI],DS:[SI]
目的操作数指出了是字节的传送,如果源串不在数据段,也可加前缀,如ES:[SI]。但这种格式不够简洁,因而不太常用。
实际上,MOVS指令的寻址方式是固定的,目的串地址为ES:[DI],源串地址为DS:[SI],因此前两种格式都将操作数直接省略了。
2、CMPS 串比较指令
(1)字节(CMPSB)操作
(ES:DI)-(DS:SI),DI±1,SI±1
(2)字(CMPSW)操作
(ES:DI)-(DS:SI),DI±2,SI±2
本条串操作指令把两个串的对应位置的字节或字相减,不保存结果,只是根据结果设置标志位。
该指令与前缀REPE联用时,可比较两个串是否相等。在每次比较过程中,一旦发现不相等,ZF=0,则终止重复执行,而不必等到整个串全部比较结束,此时 CX≠0,ZF=0。
该指令终止执行后,可根据标志ZF判断两个串是否相等。其他指令格式与串传送指令相同。
和串传送指令相同,串比较指令也涉及两个串,目的串地址为ES:[DI],源串地址为DS:[SI],因此前两种格式都将操作数直接省略了。
(3)CMPS 例题
在数据段中有一个长度为19的字符串 MESS1,还有一个长度为19的字符串MESS2,比较它们是否相等。若相等显示‘Y',否则显示‘N'。编制程序如下所示:
DATA SEGMENT
MESS1 DB 'COMPUTER SOFTWARE $ '
MESS2 DB 'COMKUTER SOFTWARE $ '
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX,DATA
MOV DS,AX
MOV ES,AX ; DS=ES
LEA SI,MESS1
LEA DI,MESS2
MOV CX,19 ; 串长
CLD ; 设置DF方向
REPE CMPSB ; 当CX=0或者ZF=1时,比较结束
JZ YES ; 如果ZF=1,说明相等,跳转到标号YES
MOV DL,'N' ; 两串不相等
JMP DISP ; 跳转到标号DISP
YES: MOV DL,'Y'
DISP: MOV AH,2
INT 21H
MOV AH,4CH
INT 21H
CODE ENDS
END START
3、SCAS 串扫描指令
(1)字节(SCASB)操作
AL-(ES:DI),DI±1
(2)字(SCASW)操作
AX-(ES:DI),DI±2
串扫描指令是把AL/AX寄存器中的内容与附加段中的由目的变址寄存器DI所指向的内存单元内容相比较,与CMPS比较指令相似,并不保存结果,只是根据结果设置标志位。
该指令与前缀REPNE联用时,可在目的串中查找有无和AL/AX寄存器中的内容相同的字节或字。在每次执行串扫描指令过程中,一旦发现相等,即ZF=1,则终止执行,此时CX≠0,ZF=1,说明已找到相同的内容,而不必等到整个串全部扫描结束。
该指令终止执行后,可根据标志位 ZF 判断是否找到。指令相关格式要求同串传送指令。
(3)SCAS 例题
在附加段中有一个字符串MESS,其长度为19,要求查找其中有无空格符,若有空格符,把首次发现的空格符改为‘#',存回该单元,并显示‘Y',否则显示‘N'。编制程序如下所示:
EXT SEGMENT
MESS DB 'COMPUTER SOFTWARE $ '
EXT ENDS
CODE SEGMENT
ASSUME CS:CODE,ES:EXT
START:
MOV AX,EXT
MOV ES,AX
LEA DI,MESS
MOV CX,19
MOV AL,20H ; 空格符
CLD
REPNE SCASB
JZ YES ; 如果ZF=1跳转到标号YES
MOV DL,'N'
JMP DISP ; 跳转到标号DISP
YES: DEC DI
MOV BYTE PTR ES:[DI],23H ; '#’送原空格位置
MOV DL,'Y'
DISP: MOV AH,2
INT 21H
MOV AH,4CH
INT 21H
CODE ENDS
END START
4、STOS 串存入指令
(1)字节(STOSB)操作
(ES:DI)←AL,DI±1
(2)字(STOSW)操作
(ES:DI)←AX,DI±2
该指令把AL/AX寄存器的内容存入由目的变址寄存器指向的附加段的某单元中,并根据DF的值及数据类型修改目的变址寄存器的内容。当它与REP联用时,可把累加器的内容存入一个连续的内存缓冲区,该缓冲区长度由CX指定,因此STOS串指令可用于初始化某一块内存区。上述有关串处理指令的特性也适合本指令。
(3)STOS 例题
写出把附加段EXT中的首地址为MESS,长度为9个字的缓冲区置为0值的程序片段:
MOV AX, EXT
MOV ES, AX
LEA DI, MESS
MOV CX, 9
MOV AX, 0
CLD
REP STOSW
REP STOSW是字操作,每次执行时DI自动+2。
5、LODS 从串中取数指令
(1)字节(LODSB)操作
AL←(DS:SI),SI±1
(2)字(LODSW)操作
AX←(DS:SI),SI±2
该指令意义不大,一般不和REP联用,因为重复执行多次的结果也只是使累加器为最后一次的值。该指令可以由MOV指令代替。
6、串处理指令的特性及用法(略)
六、程序转移指令
程序总是放在代码段,程序的执行就是指令逐条从内存的代码段被取出,由CPU进行译码、执行。
一段程序开始执行时,指令指针寄存器IP总是指向程序的第一条要执行的指令所对应的偏移地址,CPU首先根据代码段寄存器CS和指令指针寄存器IP共同表示的地址取出该条指令,送往译码器,再执行,同时,自动修改IP寄存器的值,使其指向下一条指令所在的偏移地址。重复以上过程,程序中的指令被逐条取出执行。
以上程序的执行过程是顺序执行的形式,但通常程序中总会有判断、选择、跳转发生,从而改变程序的执行流程。
1、无条件转移指令与程序的可重新定位
JMP(jmp):该指令无条件转移到指令指定的地址去执行程序。指令中必须指定转移的目标地址(或称转向地址)。
根据目标地址,可以将无条件转移指令分为段内转移和段间转移。
① 转移的目标地址如果和本条跳转指令同在一个代码段,也就是说,跳转后,CS寄存器的值并没有改变,只是IP寄存器有了改变,这叫段内转移。
② 如果转移的目标地址和本条跳转指令不在同一个代码段,也就是说,跳转后,CS寄存器的值也发生了改变,这叫段间转移。
根据目标地址是否在跳转指令中直接给出,还可以将转移指令分为直接转移和间接转移。
① 如果转移的目标地址在跳转指令中直接给出,这叫直接转移。
② 如果转移的目标地址在跳转指令中通过其他方式间接给出,这叫间接转移。
(1)段间直接转移
格式:JMP NEAR PTR OPR
操作:IP←IP+16位位移量
其中NEAR PTR为目标地址OPR的属性说明,表明是一个近(段内)跳转。
从指令的操作可以看出,IP寄存器被加上16位位移量,IP的值发生了改变,不再直接指向下一条指令,但CS寄存器并没有改变,所以是段内直接转移。
需要注意,位移量OPR是有符号数,这就意味着,位移量是负数时,IP+16位位移量之后,IP的值反而会变小,这就导致程序向后跳。如果位移量是正数时,IP的值会增加,这就导致程序向前跳。
如果转移的目标地址OPR距离本条跳转指令在-128~+127字节范围内,也可写成所谓的短转移指令:
JMP SHORT OPR
通常,目标地址OPR的属性说明可以省略,直接写成JMP OPR 即可。
注意该指令的操作是IP←IP+位移量,而不是IP←目标地址OPR。
(2)段间直接转移例题
程序重新定位下的转移指令
下面的程序中有4条指令,每条指令都为2个字节长度,假定首条指令JMP P1 的偏移地址位置为1000H,当执行该条指令后,程序跳过其后的2条MOV指令,而跳转到标号P1所指示的位置去执行ADD指令。
1000: JMP P1 ; 1000H是本条指令的所在偏移地址
1002: MOV AX,BX
1004: MOV DX,CX
P1: ADD AX,DX ; P1是标号,其值为1006H
如果把这个程序放在内存中的另一个位置:
2000: JMP P1 ; 2000H是本条指令的所在偏移地址
2002: MOV AX,BX
2004: MOV DX,CX
P1: ADD AX,DX ; P1是标号,其值为2006H
上述程序可在Debug下直接用A命令给出,分别在1000和2000两个位置。由于Debug下不能识别符号,所以P1分别用1006和2006表示,尽管如此,这两处的JMP指令的机器码依然一致,都是EB04,看来这两段程序确实完全一样。
JMP机器指令中不是直接给出目标偏移地址,而是给出相对于目标位置的位移量,用IP←当前IP+位移量的操作机制,实现了程序的可重新定位。
(3)段内间接转移
格式:JMP WORD PTR OPR
操作:IP←(EA)
其中有效地址EA值由OPR的寻址方式确定。
它可以使用除立即数方式以外的任何一种寻址方式。如果指定的是寄存器,则把寄存器的内容送到IP寄存器中,如果指定的是内存中的一个字,则把该存储单元的内容送到IP寄存器中去。
(4)段内间接转移例题
如果BX=2000H,DS=4000H,(42000H)=6050H,(44000H)=8090H,TABLE的偏移地址为2000H,分析下面四条指令单独执行后IP的值:
JMP BX ; 寄存器寻址,IP=BX
JMP WORD PTR[BX] ; 寄存器间接寻址,IP=[DS:BX]
JMP WORD PTR TABLE ; 直接寻址,IP=[DS:TABLE]
JMP TABLE[BX] ; 寄存器相对寻址,IP=[DS:(TABLE+BX)]
第一条指令执行后,IP=BX=2000H。
第二条指令执行后,IP=(DS:2000H)=(40000H+2000H)=(42000H)=6050H。
第三条指令执行后,IP=(DS:2000H)=(40000H+2000H)=(42000H)=6050H。
第四条指令执行后,IP=(DS:4000H)=(40000H+4000H)=(44000H)=8090H。
(5)段间直接转移
格式:JMP FAR PTR OPR
操作:IP←OPR的偏移地址
CS←OPR所在段的段地址
在汇编格式中 OPR 可使用符号地址,而机器语言中含有转向的偏移地址和段地址。因为IP和CS的值都被改变,所以又叫跨段直接远转移。
(6)段间间接转移
格式:JMP DWORD PTR OPR
操作:IP←(EA)
CS←(EA+2)
其中EA由OPR的寻址方式确定,它可以使用除立即数及寄存器方式以外的任何存储器寻址方式。根据寻址方式求出EA后,把指定内存字单元的内容送到IP寄存器,并把下个字的内容送到CS寄存器,这样就实现了段间的间接远跳转。
(7)段间间接转移例题
如果BX=2000H,DS=4000H,(42000H)=6050H,(42002H)=1234H,指出下面指令执行后IP和CS的值:
JMP DWORD PTR[BX]
指令执行后,IP=(DS:2000H)=(40000H+2000H)=(42000H)=6050H,CS=(42002H)=1234H。
2、条件转移指令
条件转移指令是根据上一条指令执行后,所产生的标志位来进行测试条件判别。所以在使用条件转移指令之前,应有一条能产生标志位的前导指令,如 CMP 指令。每一种条件转移指令有各自的测试条件,当满足测试条件时则转移到由指令指定的转向地址去执行那里的程序,如不满足测试条件则顺序执行下一条指令。所有的条件转移指令都不影响标志位。
(1)根据单个条件标志的设置情况转移
一般适用于测试某一次运算的结果并根据其不同特征产生程序分支作不同处理的情况:
① 指令格式:JZ OPR ; 结果为零则转移
等效指令:JE OPR ; 结果相等则转移
测试条件:ZF=1,则转移。
② 指令格式:JNZ OPR ; 结果不为零则转移
等效指令:JNE OPR
测试条件:ZF=0,则转移。
③ 指令格式:JS OPR ; 结果为负则转移
测试条件:SF=1,则转移。
④ 指令格式:JNS OPR ; 结果为正则转移
测试条件:SF=0,则转移。
⑤ 指令格式:JO OPR ; 结果溢出则转移
测试条件:OF=1,则转移。
⑥ 指令格式:JNO OPR ; 结果不溢出则转移
测试条件:OF=0,则转移。
⑦ 指令格式:JC OPR ; 进位位为l则转移
等效指令:JB OPR ; 低于则转移
等效指令:JNAE OPR ; 不高于等于则转移
测试条件:CF=1,则转移。
⑧ 指令格式:JNC OPR ; 进位位为0则转移
等效指令:JNB OPR ; 不低于则转移
等效指令:JAE OPR ; 高于等于则转移
测试条件:CF=0,则转移。
⑨ 指令格式:JP OPR ; 奇偶位为1则转移
等效指令:JPE OPR ; 偶数个1则转移
测试条件:PF=1,则转移。
⑩ 指令格式:JNP OPR ; 奇偶位为0则转移
等效指令:JPO OPR ; 奇数个1则转移
测试条件:PF=0,则转移。
上面10条指令都是根据标志寄存器中某一个标志位的状态决定是否转移,下面还有根据CX寄存器是否为零决定是否转移的指令。
(2)测试CX寄存器的值为零则转移
指令格式:JCXZ OPR ; CX寄存器为零则转移
测试条件:CX=0,则转移。
(3)比较两个无符号数,并根据比较的结果转移
① 指令:JC(JB,JNAE)OPR ; 进位位为1(低于,不高于等于)则转移
测试条件:CF=1,则转移。
② 指令:JNC(JNB,JAE)OPR ; 进位位为0(不低于,高于等于)则转移
测试条件:CF=0,则转移。
③ 指令:JBE(JNA)OPR ; 低于等于(不高于)则转移
测试条件:CF|ZF=1,即CF与ZF的或为1,则转移。
④ 指令:JNBE(JA)OPR ; 不低于等于(高于)则转移
测试条件:CF|ZF=0,即CF与ZF的或为0,则转移。
(4)比较两个有符号数,并根据比较结果转移
① 指令:JL(JNGE) OPR ; 小于(不大于等于)则转移
测试条件:SF^OF=1,即SF与OF的异或为1,则转移。
② 指令:JNL(JGE) OPR ; 不小于(大于等于)则转移
测试条件:SF^OF=0,即SF与OF的异或为0,则转移。
令:JLE(JNG) OPR ; 小于等于(不大于)则转移
测试条件:(SF^OF)|ZF=1,即SF与OF的异或为1或者ZF=1,则转移。
令:JNLE(JG) OPR ; 不小于等于(大于)则转移
测试条件:(SF^OF)|ZF=0,即SF与OF的异或为0且ZF=0,则转移。
无符号数的比较和有符号数的比较的测试条件是完全不同的。例如8位二进制数A=10101001,B=00110101,如果把它们看成无符号数,则A>B,如果把它们看成有符号数,则A<B。
无符号数比较的转移指令的测试条件是易于理解的。当两个无符号数相减时,CF位的情况说明了是否有借位的问题,有借位时,CF=1,这就是JC,JB和JNAE是等效指令的原因。
有符号数比较的转移指令的测试条件比较复杂,下面分析JL指令的情况,先比较两个有符号数的大小,CMP通过相减而产生的标志位作出判断:
CMP AX, BX
JL P1 ; 如果AX<BX,则转移到P1
有四种情况,如下所示:
① 正数A—正数B,如结果为负数,说明A<B;如结果为正数,说明A≥B。不会溢出。
② 负数A—负数B,如结果为负数,说明A<B;如结果为正数,说明A≥B。不会溢出。
③ 负数A—正数B,显然A<B,结果应为负数;如结果为正数,说明溢出。
④ 正数A—负数B,显然A>B,结果应为正数;如结果为负数,说明溢出。
只要SF和OF不相同,就说明两个有符号数A,B的比较中A<B。所以可以得出JL指令的测试条件为SF^OF=1。
(5)条件转移指令例题
有一个长为19字节的字符串,首地址为MESS。查找其中的‘空格’(20H)字符,如找到则继续执行,否则转标号NO:
MOV AL, 20H
MOV CX, 19
MOV DI, -1
LK: INC DI
DEC CX
CMP AL, MESS[DI] ; AL-[MESS+DI],不回送结果,置标志位CF/ZF
JCXZ NO ; 判断CX是否为0
JNE LK ; 判断ZF标志位是否为0,为0则跳转
YES:…
JMP EXIT
NO:…
EXIT:MOV AH, 4CH
INT 21H
当AL-[MESS+DI]的结果为0,即AL的内容与内存单元内容相等时,CMP的运行结果会将ZF标志位置1,当执行到JNE时则不会,继续执行下一条指令。
3、循环指令
(1)LOOP 循环指令
指令:LOOP OPR ; 循环
测试条件:CX≠0,则循环
(2)LOOPZ / LOOPE 当为零/相等时循环指令
指令:LOOPZ OPR ; 当为零时循环
等效指令:LOOPE OPR
测试条件:ZF=1且CX≠0,则循环
(3)LOOPNZ / LOOPNE 当不为零/不相等时循环指令
指令:LOOPNZ OPR ; 当不为零时循环
等效指令:LOOPNE OPR
测试条件:ZF=0且CX≠0,则循环
以上循环指令的操作均是:首先执行CX寄存器减1,然后根据测试条件决定是否转移。
(4)循环指令例题
在首地址为MESS长为19字节的字符串中查找‘空格’(20H)字符,如找到则继续执行,否则转标号NO。用循环指令实现程序的循环:
MOV AL, 20H
MOV CX, 19
MOV DI, -1
LK: INC DI
CMP AL, MESS[DI]
LOOPNE LK ; 当CX≠0且ZF=0时跳转
JNZ NO ; 当ZF=0时跳转
YES: …
JMP EXIT
NO: …
EXIT: MOV AH,4CH
INT 21H
…
当LOOPNE LK结束时有两种可能,即CX=0或者ZF=1,而这两种可能对应的结果是不相同的,因此在LOOPNE下方紧跟着一条JNZ的跳转指令,用以区分这两种情况。
需特别注意CX=0与ZF=1同时成立的情况,即比较的最后一个字符是相同的,所以区分以上两种情况时,需优先判断ZF=1是否成立。
第六章 伪指令与源程序格式
一、伪指令
1、处理机选择伪指令
处理机选择伪指令有以下几种:
·8086 选择8086指令系统
·286 选择80286指令系统
·286P 选择保护方式下的80286指令系统
·386 选择80386指令系统
·386P 选择保护方式下的80386指令系统
·486 选择80486指令系统
·486P 选择保护方式下的80486指令系统·
586 选择Pentium指令系统
·586P 选择保护方式下的Pentium指令系统
指令中的点“·”是需要的。这类伪指令一般放在代码段中的第一条指令前即可。汇编程序默认选择是8086指令系统。
2、段定义伪指令
汇编程序在把源程序转换为目标程序时,只能自动确定标号和变量(代码段和数据段的符号地址)的偏移地址,程序中对于段地址也要作出说明,段地址一旦说明,该段内的指令、标号和变量都属于这个段。
(1)段定义伪指令格式
segment_name SEGMENT
…
segment_name ENDS
其中segment_name由用户确定,大写的为关键字。段定义伪指令两句成对出现,两句之间为其他指令。为了确定用户定义的段和哪个段寄存器相关联,用ASSUME伪指令来实现。
(2)ASSUME伪指令格式:
ASSUME register_name: segment_name …,register_name: segment_name
其中register_name为段寄存器名,必须是CS,DS,ES和SS。而segment_name则必须是由段定义伪指令定义的段中的段名。
ASSUME伪指令只是指定把某个段分配给哪一个段寄存器,它并不能把段地址装入段寄存器中,所以在代码段中,还必须把段地址装入相应的段寄存器中。为此,还需要用两条MOV指令完成这一操作。但是,代码段不需要这样做,代码段的这一操作是在程序初始化时完成的。应该还记得,不允许对CS寄存器赋值。
(3)简化的段定义伪指令
·MODEL伪指令说明在内存中如何安排各个段,存储模型为SMALL的意思是:所有数据都放在一个64KB的数据段,所有代码都放在另一个64KB的代码段,数据和代码都为近访问。这是最常用的一种模型。
·DATA伪指令用来定义数据段,但没有给出段名,默认段名是_DATA。
@DATA表示段名_DATA,在指令中表示段地址。
(4)段定义伪指令与简化的段定义伪指令示例
# 段定义伪指令
DATA SEGMENT ; 定义数据段DATA
BUFF DB 'HELLO,WORLD!$'
DATA ENDS
CODE SEGMENT ; 定义代码段CODE
ASSUME CS:CODE,DS:DATA ; 指定段寄存器和段的关系
·386 ; 选择386指令
START:
MOV AX,DATA ; 对DS赋DATA段基地址
MOV DS,AX
LEA BX,BUFF
MOV EAX,'ABCD' ; EAX是386指令系统中的32位寄存器
MOV[BX],EAX
MOV DX,OFFSET BUFF
MOV AH,9
INT 21H
CODE ENDS
END START
# 简化的段定义伪指令
·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
3、程序开始和结束伪指令
表示源程序结束的伪操作的格式为:
END[label]
汇编程序将在遇到END时结束汇编。其中标号label指示程序开始执行的起始地址。如果是多个程序模块相连接,则只有主程序需要使用标号,其他子程序模块则只用END而不能指定标号。
4、数据定义与存储器单元分配伪指令
(1)伪指令格式
[变量] 操作码 N个操作数 [; 注释]
其中变量字段是可有可无的,它用符号地址表示。其作用与指令语句前的标号相同。但它的后面不跟冒号。
操作码字段说明所用伪操作的助记符,即伪操作,说明所定义的数据类型。常用的有以下几种。
① DB:伪操作用来定义字节,其后的每个操作数都占有一个字节(8位)。
② DW:伪操作用来定义字,其后的每个操作数占有一个字(16位,其低位字节在第一个字节地址中,高位字节在第二个字节地址中,即数据低位在低地址,数据高位在高地址)。
③ DD:伪操作用来定义双字,其后的每个操作数占有两个字(32位)。
④ DF:伪操作用来定义6个字节的字,其后的每个操作数占有48位。
⑤ DQ:伪操作用来定义4个字,其后的每个操作数占有4个字(64位),可用来存放双精度浮点数。
⑥ DT:伪操作用来定义10个字节,其后的每个操作数占有10个字节,为压缩的BCD码。
这些伪操作可以把其后跟着的数据存入指定的存储单元,形成初始化数据;或者只分配存储空间而并不确定数值。
(2)例题
① 操作数为常数、数据表达式
D_BYTE DB 10,10H
D_WORD DW 14,100H,-5,0ABCDH
D_DWORD DD 4×8
程序中默认的数据为十进制数,10H为十六进制数,用DB定义的数据的值不能超出一个字节所能表示的范围。数据10的符号地址是D_BYTE,数据10H的符号地址是D_BYTE+1。
数据可以是负数,均为补码形式存放。允许数据表达式,如4×8,等价为32。若数据第一位不是数字,应在前面加0,如0ABCDH。
② 操作数为字符串,问号‘?’仅预留空间
MESSAGE DB 'HELLO?',? ; 问号?通常被系统置0
DB 'AB',?
DW 'AB' ; 注意这里’AB’作为串常量按字类型存放
③ 用操作符复制操作数
④ 根据需要自己定义的各类数据,含义由自己决定
X1 DB 14,3 ; 十进制小数3.14
Y2 DW 1234H,5678H ; 32位数据十六进制数56781234H
Y3 DW 22,9 ; 32位数据十六进制数00090016H
5、类型属性操作符
WORD PTR ; 字类型
BYTE PTR ; 字节类型
通常访问内存变量要知道它的符号地址,以便定位,还要知道它的类型(长度),以便匹配,如果指令中不可避免地出现两个类型(长度)不匹配的操作数时,可以在指令中对该内存变量使用类型属性操作符指定访问类型。这里注意仅是“访问类型”,并不是改变了变量本身的类型,访问类型的作用只是增加了一种访问方式。
例题:在指令中用类型属性操作符指定对内存变量的访问类型,以匹配两个操作数
OPER1 DB 3,4
OPER2 DW 5678H, 9
┇
MOV AX, OPER1 ; 操作数类型不匹配
MOV BL, OPER2 ; 操作数类型不匹配
MOV [DI], 0 ; 操作数类型不明确
前两条指令操作数类型不匹配,第三条指令的两个操作数类型都不明确,所以都是错误的。解决的办法是可在指令中对操作数为内存变量的指定访问类型,以使操作数类型匹配和明确。
这三条指令可改为:
MOV AX,WORD PTR OPER1 ; 从OPER1处取一个字使AX=0403H
MOV BL,BYTE PTR OPER2 ; 从OPER2处取一个字节使BL=78H
MOV BYTE PTR[DI],0 ; 常数0送到内存字节单元
6、THIS操作符和LABEL伪操作
(1)THIS操作符
格式:THIS type
(2)LABEL伪操作
格式:name LABEL type
只是指定一个类型为type的操作数,使该操作数的地址与下一个存储单元地址相同。type在这里是BYTE或者WORD。
(3)例题
把变量定义成不同访问类型,以便指令中可灵活选用
BUF=THIS WORD
DAT DB 8,9
OPR_B LABEL BYTE
OPR_W DW 4 DUP(2)
┇
MOV AX, 1234H
MOV OPR_B,AL
MOV OPR_W+2,AX
MOV DAT+1,AL
MOV BUF,AX
表达式BUF=THIS WORD使BUF和DAT指向同一个内存单元。
LABEL伪操作使得OPR_B和OPR_W指向同一个内存单元。
7、表达式赋值伪指令“EQU”和“=”
Expression_name EQU Expression
Expression_name = Expression
上式中的表达式必须是有效的操作数格式或有效的指令助记符,此后程序中凡需要用到该表达式之处,可以用表达式名来代替,如:
VALUE EQU 4
DATA EQU VALUE+5
ADDR EQU [BP+VALUE]
此后,指令MOV AX,ADDR就代表MOV AX,[BP+4],可见,EQU伪操作的引入提高了程序的可读性,也更加易于程序的修改。
在 EQU 语句的表达式中,如果有变量或标号等其他符号出现在表达式中,必须先定义这些符号才能引用。
另一个更简洁的赋值伪操作是“=”,格式同“EQU”,区别是EQU伪操作中表达式名不允许重复定义,而“=”伪操作允许重复定义。如:
VALUE = 53
VALUE = VALUE + 89
如果把上面的两条赋值伪指令的“=”改成“EQU”,则为重复定义表达式名VALUE,是不允许的。
8、汇编地址计数器$与定位伪指令
(1)地址计数器$
在汇编程序对源程序汇编的过程中,为了按序存放程序中定义的数据变量和指令,使用地址计数器来设置当前正在汇编的指令的偏移地址。每一段的开始,地址计数器初始化为零,接着每处理一条指令,地址计数器就增加一个值,此值为该指令所需要的字节数,以安排下条指令的存放位置。
地址计数器不是硬件构成,其实就是一个16位的变量,可用来表示。当用在伪操作的参数字段时,它所表示的是地址计数器的当前值。汇编语言允许用户直接用$来引用地址计数器的值。
(2)地址计数器$例题
考察的作用,假定初值=0
ARRAY DW 3, $+7, 7
COU=$
NEW DW COU
(3)ORG伪操作
ORG伪操作用来设置当前地址计数器$的值,其格式为:
ORG constant expression
如常数表达式的值为n,则该操作指示下一个字节的存放地址为n。
(4)ORG伪操作例题
考察ORG伪操作
ORG 0
DB 3
ORG 4
BUFF DB 6
ORG $+6
VAL DB 9
(5)EVEN伪操作
EVEN伪操作使下一个变量或指令开始于偶数地址。
对于16位的变量来说,其地址为偶数时,机器内部只用一次读写操作,如果地址为奇数时要二次读写操作。但对于程序中要大量访问字单元变量时,变量始于偶数地址是有利的。例如:
EVEN
ARRAY DW 800 DUP(?)
(6)ALIGN伪操作
ALIGN伪操作使下一个变量的地址从4的倍数开始,这可以用来保证双字数组边界从4的倍数开始,其格式为:
ALIGN boundary
其中boundary必须是2的幂。例如:
ALIGN 8
ARRAY DW 800 DUP(?)
9、基数控制伪指令
汇编程序默认的数为十进制数,所以在程序中使用其他基数表示的常数时,需要专门给以标记如下:
(1)二进制数
由一串0和1组成,其后跟以字母B,如00101001B。
(2)十进制数
由0~9的数字组成的数,一般情况下,后面不必加上标记,在指定了其他基数的情况下,后面跟字母D,例如23D。
(3)十六进制数
由0~9及A~F组成的数,后面跟字母H。这个数的第一个字符必须是0~9,所以如果第一个字符是A~F时,应在其前面加上数字0,如0FFFFH。
(4)RADIX伪操作
可以把默认的基数改变为2~16范围内的任何基数。其格式如下:
.RADIX expression
其中表达式用来表示基数值(用十进制数表示)。
注意在用.RADIX把基数定为十六进制后,十进制数后面都应跟字母D。在这种情况下,如果某个十六进制数的末字符为D,则应在其后跟字母H,以免与十进制数发生混淆。
10、过程定义伪指令
子程序又称过程,可以把一个程序写成一个过程或多个过程,这样可以使程序结构更加清晰,基本的过程定义伪指令的格式为:
procedure_name PROC Attribute
┇
procedure_name ENDP
过程名procedure_name为标识符,起到标号的作用,是子程序入口的符号地址。
属性Attribute是指类型属性,可以是NEAR或FAR。
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
二、语句格式
程序中用得最多的是指令和有关数据定义的伪指令,这些语句由4项组成,格式如下:
[name] operation operand [; comment]
[名字] 操作 操作数 [; 注释]
名字项是一个符号,可以是指令的标号,也可以是变量名。
操作项是一个操作码的助记符,它可以是指令、伪指令或宏指令名。
操作数项由一个或多个表达式组成,它提供该操作所要求的操作数或相关信息。
注释项用来说明程序或语句的功能。上面带方括号的两项是可选项。各项之间必须用‘空格’隔开。
1、名字项和操作项
(1)名字项
名字项用下列字符来表示:
字母A~Z、数字O~9、专用字符?,·,@,-,$
除数字外,所有字符都可以放在源语句的第一个位置。名字中如果用到·,则必须是第一个字符。
名字项可以是标号或变量,用来表示本语句的符号地址。如果是指令的标号,后面跟冒号。
作为一个地址符号,显然具有段、偏移及类型3个基本属性,根据某变量的这三个基本属性值,在程序中才能访问到该变量。
段属性,定义该地址符号的段起始地址,此值必须在一个段寄存器中。
偏移属性,是从段起始地址到定义该地址符号的位置之间的字节数。
类型属性,对于标号,用来指出该标号是在本段内引用还是在其他段中引用的。如是段内引用,则称为NEAR。如是段外引用,则称为FAR;对于变量,类型属性定义该变量所保留的字节数。如BYTE(1个字节)、WORD(2个字节)、DWORD(4个字节)、FWORD(6个字节)、QWORD(8个字节)、TBYTE(10个字节)。
※ 变量与标号
① 区别:变量是为指令提供的操作数,标号是为指令提供标识;
② 属性:二者都有段属性、偏移属性和类型属性;
③ 获取属性值:
MOV BX, SEG VAL ;取段属性
MOV BX, OFFSET VAL ;取偏移属性
MOV BX, TYPE VAL ;取类型属性
(2)操作项
操作项可以是指令、伪指令或宏指令的助记符。
对于指令,汇编程序将其翻译为机器语言。
对于伪指令,汇编程序将根据其所要求进行处理。
2、表达式和操作符
操作数项由一个或多个表达式组成。
对于指令,操作数项一般给出操作数地址,通常不超过两个;对于伪操作或宏指令,则给出它们所要求的参数。
操作数项可以是常数、寄存器、标号、变量,还可以是表达式,而表达式是常数、寄存器、标号、变量与一些操作符(运算符)相组合的序列。
(1)算术操作符
算术操作符有+,-,*,/和MOD。
要注意的是,算术操作符在表达式中的使用,其结果必须有明确的物理意义时才是有效的。
(2)算术操作符例题
算术操作符的使用
设有如下定义:
ORG 0
VAL=4
DA1 DW 6,2,9,3
DA2 DW 15,17,24
COU=$-DA2
上面定义的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 ; 地址表达式,汇编成MOV AX,[4]
MOV AX,[DA1+VAL] ; 地址表达式,汇编成MOV AX,[4]
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
MOV BX,COU ; 得到DA2区的字节数,汇编成MOV BX,6
(3)逻辑与逻辑移位操作符
逻辑操作符有AND,OR,XOR和NOT;逻辑移位操作符有SHL和SHR。它们都是按位操作的。注意逻辑与逻辑移位指令和逻辑与逻辑移位操作符的区别,逻辑与逻辑移位操作符只能用于数字表达式中。格式为:
expression 操作符 number
(4)逻辑与逻辑移位操作符例题
① 逻辑操作符的使用
ARY DW 8VAL=4
MOV AX,BX AND 0FFH ; 错,BX AND VAL须用指令实现
MOV AX,ARY AND 0FFH ; 错,ARY AND VAL须用指令实现
MOV AX,VAL AND 0F0H ; 汇编成MOV AX,0
AND AX,VAL OR 0F0H ; 汇编成AND AX,0F4H
② 移位操作符的使用
ARY DW 8
VAL=4
MOV AX,BX SHL 2 ; 错,BX 左移须用指令实现
MOV AX,ARY SHL 2 ; 错,ARY 左移须用指令实现
MOV AX,VAL SHL 2 ; 汇编成MOV AX,10H
MOV AX,8 SHL 2 ; 汇编成MOV AX,20H
MOV AX,VAL SHL 15 ; 汇编成MOV AX,00H
(5)关系操作符
关系操作符用来对两个操作数的大小关系作出判断。
六个关系操作符是EQ(相等)、NE(不等)、LT(小于)、GT(大于)、LE(小于等于)、GE(大于等于)。
关系操作符的两个操作数必须都是数字,或是同一段内的两个存储器地址。
计算结果为逻辑值,如结果为真,表示为FFFFH;结果为假,则表示为0。
(6)关系操作符例题
关系操作符的使用
VAL=4
MOV AX, BX GT 2 ; 错,BX 是否大于2须用指令实现判断
MOV AX, VAL GE 2 ; 汇编成MOV AX,FFFFH
MOV AX, 8 LE VAL ; 汇编成MOV AX,0
(7)数值回送操作符
① TYPE
格式:TYPE expression
如果该表达式是变量,则汇编程序将回送该变量的以字节数表示的类型:DB为1,DW为2,DD为4,DF为6,DQ为8,DT为10。如果表达式是标号,则汇编程序将回送代表该标号类型的数值,NEAR为-1,FAR为-2。如果表达式为常数则回送0。
② LENGTH
格式:LENGTH variable
变量用DUP复制的,则回送总变量数,其他为1,但嵌套的DUP不计。所以,对于使用嵌套的DUP复制的数据不能据此得到正确的总变量数。
③ SIZE
格式:SIZE variable
变量用DUP复制的,则回送总字节数,其他为单个变量的字节数,但嵌套的DUP不计。所以,对于使用嵌套的DUP复制的数据不能据此得到正确的总字节数。
④ OFFSET
格式:OFFSET variable或label
回送变量或标号的偏移地址。
⑤ SEG
格式:SEG variable或label
回送变量或标号的段地址。
(8)数值回送操作符例题
数值回送操作符的使用
设有如下定义:
ORG 0
VAL=4
ARR DW 4 DUP(3)
BUF DW 4 DUP(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 BUF ; 汇编成MOV AX,4
MOV AX,LENGTH DAT ; 汇编成MOV AX,1
MOV AX,SIZE ARR ; 汇编成MOV AX,8
MOV AX,SIZE BUF ; 汇编成MOV AX,8(不是32)
MOV AX,SIZE DAT ; 汇编成MOV AX,2
MOV AL,SIZE STR ; 汇编成MOV AX,1
MOV AX,OFFSET ARR ; 不完整的机器指令
MOV BX,SEG ARR ; 不完整的机器指令
(9)操作符的优先级别(从高到低排列)
① 在圆括号中的项,方括号中的项,结构变量(变量,字段),LENGTH,SIZE,WIDTH,MASK。
② 名:(段取代)。
③ PTR,OFFSET,SEG,TYPE,THIS及段操作符。
④ HIGH和LOW。
⑤ 乘法和除法:*,/,MOD,SHL,SHR。
⑥ 加法和减法:+,-。
⑦ 关系操作:EQ,NE,LT,LE,GT,GE。
⑧ 逻辑:NOT。
⑨ 逻辑:AND。
⑩ 逻辑:OR,XOR。
⑪ SHORT。
第七章 分支与循环程序设计
一、分支程序设计
1、分支程序结构
2、单分支程序
在IF-THEN-ELSE结构中最简单的情况是只需处理IF-THEN,这就是单分支结构。
例题:双字长数存放在DX和AX寄存器中(高位在DX),求该数的绝对值(用16位指令)
算法分析:首先判断数的正负,如果是正数(首位为0),不需处理,如果是负数(首位为1),则对该数求补,即反码加1。
程序如下:
CODE SEGMENT
ASSUME CS:CODE
START:
TEST DX,8000H ; 测试数的正负
JZ EXIT ; 不为负数就退出
NOT AX
NOT DX
ADD AX,1
ADC DX,0
EXIT:
MOV AH,4CH
INT 21H
CODE ENDS
END START
在Debug下修改寄存器DX的值分别为正数1000H和负数9000H,其与8000H做TEST运算的结果,可得ZF标志位分别取1和0:
3、复合分支程序
如果在分支结构中又出现分支,这就是复合分支结构。
例题:从键盘输入一位十六进制数,并将其转换为十进制数显示输出
算法分析:键盘输入的十六进制数有以下三种情况需要分别处理:
(1)为数字(30H~39H)时,可不必处理,直接输出;
(2)为大写A~F(41H~46H)时,可减11H;
(3)为小写a~f(61H~66H)时,可减31H;
这样就得到30H~35H(0~5的ASCII码),再输出2位十进制数字。其他输入则为非法输入,退出程序。由此可见,本例实际上就是相当于求分段函数。
程序如下:
CODE SEGMENT
ASSUME CS:CODE
START:
MOV AH,1 ; 键盘输入
INT 21H
CMP AL,30H
JL EXIT ; 非法输入
CMP AL,39H
JLE DIG ; 输入是数字0~9
CMP AL,41H
JL EXIT ; 非法输入
CMP AL,46H
JLE PRINT ; 输入是大写A~F
CMP AL,61H
JL EXIT ; 非法输入
CMP AL,66H
JG EXIT ; 非法输入
SUB AL,31H
JMP OUT1 ; 输入是小写A~F
PRINT:
SUB AL,11H
OUT1:
MOV DL,31H ; 输出字符1
MOV AH,2
PUSH AX ; 暂存AX
INT 21H ; INT指令改写了AX
POP AX ; 恢复AX
DIG:
MOV DL,AL ; 输出个位
MOV AH,2
INT 21H
EXIT:
MOV AH,4CH ; 程序终止并退出
INT 21H
CODE ENDS
END START
输入A字符,对A的ASCII码41H减11H,得到待显示的30H保存在AL中,执行2号功能的INT 21H之后AL变为31H,用POP指令恢复AL中内容后又变为30H。
4、多分支程序
如果在分支结构中有超过两个以上的多个可供选择的分支,这就是多分支结构。
例题:根据键盘输入的一位数字(1~4),使程序转移到4个不同的分支中去,以显示键盘输入的数字
算法分析:建立一个分支向量表 branch,集中存放4个分支的偏移地址,因偏移地址为16位,所以每两个字节存放一个偏移地址。根据输入的数字指向分支向量表,从表中取出对应分支的偏移地址,用JMP branch[BX]指令间接寻址方式转向对应分支。
程序如下:
CODE SEGMENT
ASSUME CS:CODE,DS:CODE
START:
MOV AX,CODE ; DS=CS
MOV DS,AX
MOV AH,7 ; 键盘输入无回显
INT 21H
CMP AL,31H
JL EXIT ; 非法输入
CMP AL,34H
JG EXIT ; 非法输入
MOV DL,AL ; 放入DL,待显示
MOV BL,AL
SUB BL,31H ; 转换ASCII码为数值
SHL BL,1 ; (BL)×2,指向分支向量表中某地址
MOV BH,0
JMP BRANCH[BX] ; 转向分支
R1:
MOV AH,2
INT 21H ; 显示键盘输入的数字
JMP EXIT
R2:
MOV AH,2
INT 21H
JMP EXIT
R3:
MOV AH,2
INT 21H
JMP EXIT
R4:
MOV AH,2
INT 21H
JMP EXIT
EXIT:
MOV AH,4CH ; 程序终止并退出
INT 21H
BRANCH
DW R1
DW R2
DW R3
DW R4
CODE ENDS
END START
该程序在代码段的最后定义了分支向量表,汇编程序进行汇编时,各标号的有效地址可以确定,分支向量表首地址branch的有效地址可以确定。
其后定义的4个字的值就是四个分支标号的有效地址,可用D命令查看,结果如下图所示。该程序也可以定义一个数据段,并把地址表branch定义在数据段。
用分支向量表方法处理多分支程序,可以简单直接地实现分支的转移,避免了大量的比较和条件转移指令,使程序显得简洁紧凑。
二、循环程序设计
1、循环程序结构
循环程序可以由以下三部分组成:
循环初始状态:为循环作准备,设置循环初始值,如地址指针和计数器的值。
循环体:重复执行的一段程序,并修改循环控制条件,如修改地址指针、计数器的值。
循环控制:判断循环条件,控制结束循环或继续循环。
2、计数循环程序
计数循环是基本的循环组织方式,用循环计数器的值来控制循环。
计数循环:循环的次数事先已经知道,用一个变量(寄存器或存储器单元)记录循环的次数(称为循环计数器),可以采用加法或减法计数。进行加法计数时,循环计数器的初值设为0,每循环一次将它加1,将它和预定次数比较来决定循环是否结束;进行减法计数时,循环计数器的初值直接设为循环次数,每循环一次将计数器减1,计数器减为0时,循环结束。如果用减法计数时,可以使用LOOP指令,该指令自动修改减法计数器CX的值,并实现循环控制。循环次数是有限的。
例题:把BX寄存器中的二进制数用十六进制数格式显示输出
算法分析:BX寄存器每4位表示一位十六进制数位,从左到右循环移位,每移4位,就把要显示的4位二进制位移到最右边。取出最右边的4位,加上30H,转换成8位ASCII字符码。因显示输出的十六进制数是数字(30H~39H)和A~F(41H~46H),所以当8位二进制数大于39H时,应再加上7。程序采用计数循环,计数值为4。
程序如下:
CODE SEGMENT
ASSUME CS:CODE
START:
MOV CX,4
SHIFT:
ROL BX,1 ; 连续循环左移4位
ROL BX,1
ROL BX,1
ROL BX,1
MOV AL,BL
AND AL,0FH ; 取最右4位
ADD AL,30H ; 转为ASCII
CMP AL,39H
JLE DIG ; 是0~9则转DIG
ADD AL,7 ; 是A~F
DIG:
MOV DL,AL
MOV AH,2
INT 21H
LOOP SHIFT
MOV AH,4CH
INT 21H
CODE ENDS
END START
该程序因没有对BX赋值,初值可能为0,所以需要在调试状态下先设置BX的值再运行程序。
3、条件循环程序
在循环程序中,我们已经看到有时候每次循环所做的操作可能不同,即循环体中有分支的情况,需要依据某一个标志来决定做何操作。标志位为1表示要做操作A,标志位为0表示要做操作B,我们可把这种标志字称为逻辑尺。
条件循环:循环的次数事先无法确定或无需确定,每次循环开始前或结束后测试某个条件,根据这个条件是否满足来决定是否继续下一次循环。这种情况可以使用条件转移指令以实现循环控制。但这种循环有可能出现死循环。
例题:先从键盘输入8位二进制数作为逻辑尺。再从键盘输入一个英文字母,根据逻辑尺当前的最高位标志显示输出该英文字母的相邻字符,标志位为0则显示其前趋字符,标志位为1则显示其后继字符。显示相邻字符后,逻辑尺循环左移一位,再接收下一个英文字母的输入,并依据逻辑尺显示相邻字符,直到回车键结束程序
算法分析:8位二进制数的输入构成一个8次循环,把输入整合到8位寄存器BL中。键盘输入一个英文字母后依据逻辑尺的最高位标志显示相邻字符,把最高位移到CF位,以CF位决定显示,构成一个条件循环,以回车键退出循环。
程序如下:
CODE SEGMENT
ASSUME CS:CODE
START:
MOV BX,0 ; 初始化
MOV CX,8
INLOG:
MOV AH,1 ; 键盘输入0/1
INT 21H
CMP AL,30H
JB EXIT ; 非法输入
CMP AL,31H
JA EXIT ; 非法输入
SUB AL,30H ; 输入是0/1
SHL BL,1
ADD BL,AL
LOOP INLOG
MOV AH,2
MOV DL,10 ; 输出换行
INT 21H
INCHR:
MOV AH,1 ; 键盘输入字母
INT 21H
CMP AL,13
JE EXIT ; 回车键
MOV DL,AL
ROL BL,1
JNC K30 ; 是0 则转K30
INC DL
JMP PUTC
K30:
DEC DL
PUTC:
MOV AH,2
INT 21H
JMP INCHR
EXIT:
MOV AH,4CH ; 程序终止并退出
INT 21H
CODE ENDS
END START
4、条件计数循环程序
条件计数循环:循环条件有两个因素,即某个条件和最大循环次数。实际循环次数事先并不确定,但循环可能的最大次数是可以确定的。每次循环开始前或结束后测试这两个条件,如果条件都满足则继续下一次循环。但无论怎样,循环的次数不会超过预定的最大次数。这种情况可以采用减法计数,使用LOOPNE或LOOPE指令,来判断循环条件并自动修改减法计数器CX的值以实现循环控制。
例题:设置键盘缓冲区为16个字节,从键盘输入一串字符,然后再从键盘输入一个单个字符,查找这个字符是否在字符串中出现,如果找到,显示该字符串,否则显示“NOT FOUND”。
算法分析:该程序使用DOS系统功能调用(INT 21H)10号功能实现键盘缓冲区输入,使用1号功能实现单个字符输入,使用9号功能实现字符串显示输出。定义键盘缓冲区大小为16个字节(含回车),缓冲区首字节存放16,接下来存放实际输入的字符个数(不含回车)和输入的字符。程序采用循环结构实现查找,最大计数值为实际输入的字符个数。
程序如下:
DATA SEGMENT
BUFFER DB 16,?,16 DUP(?),13,10,'$'
INPUTS DB 13,10,'INPUT STRING:$'
GETC DB 13,10,'INPUT CHAR:$'
OUTPUT DB 13,10,'NOT FOUND$'
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX,DATA ; DS赋值
MOV DS,AX
LEA DX,INPUTS ; 信息提示输入串
MOV AH,9
INT 21H
LEA DX,BUFFER ; 键盘输入串到缓冲区
MOV AH,10
INT 21H
LEA DX,GETC ; 信息提示输入字符
MOV AH,9
INT 21H
MOV AH,1 ; 输入字符到AL
INT 21H
MOV BL,AL ; 保存到BL
LEA DI,BUFFER+1 ; DI作为指针指向缓冲区
MOV CL,BUFFER+1 ; CX设置计数值
MOV CH,0
SEEK:
INC DI
CMP BL,[DI]
LOOPNE SEEK ; 未完且没找到,转SEEK继续循环
JNE NOF ; 没找到,转NOF输出’NOT FOUND'
MOV DL,10 ; 输出换行
MOV AH,2
INT 21H
LEA DX,BUFFER+2 ; 指向缓冲区,输出字符串
MOV AH,9
INT 21H
JMP EXIT
NOF:
LEA DX,OUTPUT
MOV AH,9
INT 21H
EXIT:
MOV AH,4CH
INT 21H
CODE ENDS
END START
程序中要注意数据段中的缓冲区和各串变量的定义。回车(13D)和换行(10D)是为了显示信息不会被覆盖。程序中的查找也可以用串处理指令实现。
5、多重循环程序
如果在循环结构中有超过两个以上的多个嵌套的循环,这就是多重循环结构。
例题:显示输出20H~7EH的ASCII字符表。每行16个字符
算法分析:20H~7EH的ASCII字符共有95个,需6行显示。该程序需两重循环,内循环输出每行16个字符,循环计数初值为16,程序终止条件是显示最后一个字符。这里我们不用流程图而用高级语言程序来描述算法,也可以导出汇编语言程序。
高级语言程序如下:
first=20h
last=7eh
char=first
x=1 ; 行号
y=1 ; 列号
do while char<last
k=16
do while k>0 and char<last
@ x,y say char
char=char+1
y=y+1
k=k-1
end do
x=x+1
y=1
end do
汇编语言程序如下:
CODE SEGMENT
ASSUME CS:CODE
K=16
FIRST=20H
LAST=7EH
START:
MOV DX,FIRST ; 从第一个开始
A10:
MOV CX,K ; 每行16个
A20:
MOV AH,2
INT 21H
CMP DL,LAST ; 是最后一个则退出
JE EXIT
PUSH DX ; 暂存DX
MOV DL,20H ; 空2格
INT 21H
INT 21H
POP DX ; 恢复DX
ADD DX,1
LOOP A20 ; 进入内循环
PUSH DX ; 暂存DX
MOV DL,13 ; 回车
INT 21H
MOV DL,10 ; 换行
INT 21H
POP DX ; 恢复DX
LOOP A10 ; 进入外循环
EXIT:
MOV AH,4CH
INT 21H
CODE ENDS
END START