为了描述上的简洁,使用一个描述性的符号“()”来表示一个寄存器或一个内存单元中的内容。比如:

(ax)表示ax中的内容、(al)表示al中的内容;

(20000H)表示内存20000H单元的内容(()中的内存单元的地址为物理地址);

((ds)*16+(bx))表示: ds 中的内容为ADR1,bx中的内容为ADR2,内存ADR1×16+ADR2单元的内容。

也可以理解为: ds中的ADR1作为段地址,bx中的ADR2作为偏移地址,内存ADR1:ADR2单元的内容。

注意,“( )”中的元素可以有3种类型:①寄存器名;②段寄存器名;③内存单元的物理地址(一个20位数据)。比如:

(ax)、(ds)、(al)、(cx)、(20000H)、((ds)*16+(bx))等是正确的用法;

(2000:0)、((ds):1000H)等是不正确的用法。

“(X)”所表示的数据有两种类型:①字节;②字。是哪种类型由寄存器名或具体的运算决定,比如:

(al)、(bl)、(cl)等得到的数据为字节型;(ds)、(ax)、(bx)等得到的数据为字型。

(al)=(20000H),则(20000H)得到的数据为字节型;(ax)=(20000H),则(20000H)得到的数据为字型。

约定符号idata表示常量

我们在Debug中写过类似的指令:mov ax,[0],表示将 ds:0 处的数据送入ax中。指令中,在“[…]”里用一个常量0表示内存单元的偏移地址。以后,我们用idata表示常量。比如:

mov ax,[idata]就代表mov ax,[1]、mov ax,[2]、mov ax,[3]等。

mov bx,idata就代表mov bx,1、mov bx,2、mov bx,3等。

mov ds,idata就代表mov ds,1、mov ds,2等,它们都是非法指令。

==SA:EA 是段地址:偏移地址的别名==

5.1 [BX]

mov ax,[bx] 功能: ==bx中存放的数据作为一个偏移地址EA==,段地址SA默认在ds 中,将SA:EA处的数据送入ax中。

即: (ax)=((ds)*16+(bx))。

5.2 Loop指令 [CX]

loop 指令的格式是: loop标号,CPU 执行loop指令的时候,要进行两步操作,

①(cx)=(cx)-1;

②判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。

从上面的过程中,可以总结出用cx和loop指令相配合实现循环功能的3个要点:

(1)在cx中存放循环次数;

(2)loop指令中的标号所标识地址要在前面;

(3)要循环执行的程序段,要写在标号和 loop指令的中间。

用cx和 loop指令相配合实现循环功能的程序框架如下:

1
2
3
	mov cx,循环次数
s:  循环执行的程序段
	loop s

5.3 在Debug中跟踪用loop指令实现的循环程序 [DX:累加寄存器]

用循环累加来实现乘法,用哪个寄存器进行累加?

将ffff:0006单元中的数赋值给ax,用dx进行累加。先设(dx)=0,然后做3次(dx)=(dx)+(ax)。

==为什么要把ffff:0006单元中的数先赋值给ax,而不直接在dx中累加呢?==

(1)ffff:0006中的数据是8位的,不能直接加到16位寄存器dx中

(2)也不能在dl中累加,因为dl是8位寄存器,能容纳的数据的范围在 0~255 之间,累加后很有可能造成进位丢失。

所以一种解决方法是得用一个16位寄存器来做中介。将内存单元中的8位数据赋值到一个16位寄存器ax中,再将ax中的数据加到dx上,从而使两个运算对象的类型匹配并且结果不会超界。

指令mov ax,0ffffh

我们知道,大于9FFFh 的十六进制数据A000H、A001H…CO00H、c001H…FFFEH、FFFFH等,在书写的时候都是以字母开头的。而==在汇编源程序中,数据不能以字母开头,所以要在前面加0==。比如,9138h在汇编源程序中可以直接写为“9138h”,而==A000h在汇编源程序中要写为“0A000h”==。

Debug

-g 0012 直接执行到指定位置

”-g 0012“它表示执行程序到当前代码段(段地址在CS 中)的0012h处。也就是说“g 0012”将使Debug从当前的CS:IP指向的指令执行,一直到(IP)=0012h 为止。具体的情况如图5.12所示。

image-20211201162709412

-p 将循环一次执行完

希望将循环一次执行完,可以使用p命令来达到目的。再次遇到loop指令时,使用p命令来执行,Debug就会自动重复执行循环中的指令,直到(cx)=0为止。具体情况如图5.14所示。

image-20211201163104740

当然,也可以用g命令来达到目的,可以用“g 0016”直接执行到CS:0016处。具体情况如图5.15所示。

image-20211201163129329

5.4 Debug和汇编编译器masm对指令的不同处理

我们在Debug中和源程序中写入同样形式的指令:“mov al,[0]”、“mov bl,[1]”、“mov cl,[2]”、“mov dl,[3]”,但 Debug和编译器对这些指令中的“[idata]”却有不同的解释。

Debug 将它解释为“[idata]”是一个内存单元,“idata”是内存单元的偏移地址;

而编译器将“[idata]”解释为“idata”。

比较一下汇编源程序中以下指令的含义。

mov al,[0]”,含义:(al)=0,将常量0送入al中(与mov al,0含义相同);

mov al,ds:[0]”,含义:(al)=((ds)*16+0),将内存单元中的数据送入al 中;

mov al,[bx]”,含义:(al)=((ds)*16+(bx)),将内存单元中的数据送入al 中;

mov al,ds:[bx]”,含义:与“mov al,[bx]”相同。

从上面的比较中可以看出:

(1)在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须用“[...]”来表示内存单元,如果在“[]”里用一个常量idata直接给出内存单元的偏移地址,就要在“[]”的前面显式地给出段地址所在的段寄存器。比如

mov al,ds:[0]

这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:” “cs:” “ss:” “es:”,在汇编语言中称为段前缀

如果没有在“[]”的前面显式地给出段地址所在的段寄存器,比如

mov al,[0]

那么,编译器masm将把指令中的“[idata]”解释为“idata”。

(2)如果在“[]”里用寄存器,比如bx,间接给出内存单元的偏移地址,则段地址默认在ds中。当然,也可以显式地给出段地址所在的段寄存器。

5.5 loop和[bx]的联合应用

image-20211201170648171

5.7 一段安全的空间

(1)我们需要直接向一段内存中写入内容;

(2)这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作很可能引发错误;

(3)DOS 方式下,一般情况,0:200~0:2ff 空间中没有系统或其他程序的数据或代码;

(4)以后,我们需要直接向一段内存中写入内容时,就使用0:200~0:2ff这段空间。

image-20211201171723905

5.8 段前缀的使用

将内存 ffff:0~ffff:b 单元中的数据复制到 0:200~0:20b 单元中

分析:0:200~0:20b 单元等同于 0020:0~0020:b 单元,它们描述的是同一段内存空间。

image-20211201172701947

因源始单元 ffff:X 和目标单元 0020:X 相距大于64KB,在不同的64KB段里,程序5.8中,每次循环要设置两次ds。这样做是正确的,但是效率不高。我们可以使用两个段寄存器分别存放源始单元 ffff:X 和目标单元 0020:X 的段地址,这样就可以省略循环中需要重复做12次的设置ds的程序段。

改进的程序如下。

image-20211201172746677