[bx]和内存单元的描述
要完整地描述一个内存单元,需要两种信息:
内存单元的地址
内存单元的长度
[bx] 也表示一个内存单元,它的偏移地址在 bx 中。
mov ax,[bx]
:将一个内存单元的内容送入ax,这个内存单元的长度为2字节,偏移地址在bx中,段地址在ds中。
mov al,[bx]
:将一个内存单元的内容送入al,这个内存单元的长度为1字节,偏移地址在bx中,段地址在ds中。
我们定义的描述性符号:“()”
我们用 ()
来表示一个寄存器或一个内存单元中的内容。(()
中的内存单元的地址为物理地址)
假设 ds 中的内容为 ADR1,bx 中的内容为 ADR2,则 ((ds)*16+(bx))
表示:内存 ADR1:ADR2 单元的内容。
()
中的元素可以有3中类型:
- 寄存器名
- 段寄存器名
- 内存单元的物理地址(一个20位数据)
(X)
所表示的数据有两种类型:字节、字。
(al)、(bl)、(cl)等得到的数据为字节型;(ax)、(bx)、(cx)等得到的数据为字型。
约定符号idata表示常量
以后我们用 idata
表示常量。
比如:mov ax,[idata]
就代表 mov ax,[1]
,mov ax,[2]
,mov ax,[3]
等。
[BX]
程序和内存中的情况如图所示,写出程序执行后,21000H~21007H单元中的内容。
P.S. inc bc
的含义是bx中的内容加1。
执行前:
执行后:
这里用到的都是前几章的知识,就不写分析了。
Loop指令
loop指令的格式是:loop 标号
CPU执行loop指令的时候,要进行两步操作:
- (cx) = (cx) - 1
- 判断 cx 中的值,不为零则转至标号处执行程序,如果为零则向下执行
通常我们用 loop 指令来实现循环功能,cx 中存放循环次数。
任务1:编程计算2^2,结果存在ax中。
assume cs:code |
任务2:编程计算2^3。
assume cs:code |
任务3:编程计算2^12
assume cs:code |
按照我们的算法,计算 2^12 需要11条重复的指令 add ax,ax
,我们显然不想这样写程序,这里,可用 loop 来简化我们的程序。
程序5.1
assume cs:code |
程序分析
标号:在汇编语言中,标号代表一个地址,程序5.1中有一个标号 s
。它实际标识了一个地址,这个地址处有一条指令:add ax,ax
。
loop s
:执行 loop s
时,首先要将(cx)减1,然后若(cx)不为0,则向前转至 s 处执行 add ax,ax
。所以,可以利用 cx 来控制 add ax,ax
的执行次数。
用 cx 和 loop 指令相配合实现循环功能的3个要点:
在 cx 中存放循环次数;
loop 指令中的标号所标识地址要在前面;
要循环执行的程序段,要写在标号和 loop 指令的中间。
用 cx 和 loop 指令相配合实现循环功能的程序框架如下
mov cx,循环次数 |
练习:用加法计算 123*236,结果存在 ax 中。
assume cs:code |
该程序执行了236次+123,其实执行123次+236会比上面的程序更好。
在Debug中跟踪用loop指令实现的循环程序
问题:计算 ffff:0006 单元中的数乘以3,结果存储在 dx 中。
程序
assume cs:code |
Debug跟踪(部分)
这两条指令执行后,(dx) = 0,完成对累加寄存器的初始化;(cx) = 3,完成了对循环计数寄存器的初始化。下面开始循环程序段的执行。
这是第一次循环。
CPU执行 loop 0012
,第一步先将 (cx) 减1,(cx)=2,第二步因 (cx) 不等于0,将 IP 设为 0012h。
接着,重复执行 add dx,ax
和 loop 0012
,直到 (cx) = 0为止。
在完成最后一次 add dx,ax
后,(dx) = 93h,此时 dx 中为累加计算 (ax)*3 的最后结果。
假设标号 s 前的指令,已经确定在逻辑上完全正确,我们不想再一步步跟踪,只想跟踪循环的过程,可以使用Debug命令 g
一次执行完标号 s
前的指令。
循环程序段从 CS:0012 开始, g CS:0012
表示执行程序到当前代码段(段地址在CS中)的0012h处。
Debug执行 g 0012
后,CS:0012 前的程序段被执行,从各个相关的寄存器中的值,我们可以看出执行的结果。
那么,如果我们希望循环一次执行完,可以使用 p
命令来执行,Debug就会自动重复执行循环中的指令,直到 (cx)=0 为止。
在遇到 loop
时,用 p
命令执行,Debug自动重复执行 loop 0012
和 add dx,ax
两条指令,直到 (cx)=0。
当然也可以使用 g 0016
直接执行到 CS:0016 处来达到目的。
Debug和汇编编译器masm对指令的不同处理
Debug 和 masm对 mov al,[0]
等指令有不同的解释
Debug对 mov al,[0]
等指令的解释
masm对 mov al,[0]
等指令的解释
Debug的解释:[idata] 是一个内存单元,idata 是内存单元的偏移地址;
编译器的解释:[idata] 是 idata。
如何在源程序中实现将内存 2000:0、2000:1、2000:2、2000:3 单元中的数据送入 al,bl,cl,dl中?
比较一下汇编源程序中以下指令的含义:
总结:
- 在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须用
[...]
来表示内存单元,如果在[]
里用一个常量 idata 直接给出内存单元的偏移地址,就要在[]
的前面显示地给出段地址所在的段寄存器。比如:mov al,ds:[0]
- 如果在
[]
里用寄存器,比如 bx,间接给出内存单元的偏移地址,则段地址默认在 ds 中。当然,也可以显示地给出段地址所在的段寄存器。
loop和[bx]的联合应用
问题:计算 ffff:0~ffff:b
单元中的数据的和,结果存储在 dx 中。
两种方法:1. (dx) = (dx) + 内存中的8位数据 2. (dl) = (dl) + 内存中的8位数据
第一种方法中的问题是两个运算对象的类型不匹配,第二种方法中的问题是结果有可能超界。
所以,我们用一个16位寄存器来做中介,将内存单元中的8位数据赋值到一个16位寄存器ax中,再将ax中的数据加到dx。由于要加12次,我们用loop来实现。
程序5.6
assume cs:code |
段前缀
mov ax,ds:[bx] |
这些访问内存单元的指令中,显式指明内存单元段地址的 ds:
cs:
ss:
es:
,在汇编语言中称为段前缀。
一段安全的空间
在8086模式中,随意向一段内存空间写入内容式很危险的,因为这段空间中可能存放着重要的系统数据或代码。
- 我们需要直接向一段内存中写入内容;
- 这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作系统很可能引发错误;
- DOS方式下,一般情况,
0:200~0:2ff
空间中没有系统或其他程序的数据或代码; - 以后,我们需要直接向一段内存中写入内容时,就使用
0:200~0:2ff
这段空间。
段前缀的使用
问题:将内存 ffff:0~ffff:b
单元中的数据复制到 0:200~0:20b
单元中。
程序5.8
assume cs:code |
实验4 [bx]和loop的使用
编程,向内存 0:200~0:23F
依次传送数据 0~63(3FH),程序中只能使用9条指令,9条指令中包括 mov ax,4c00h
和 int 21h
。
assume cs:code |