[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
code segment
mov ax,2
add ax,ax

mov ax,4c00h
int 21h
code ends
end

任务2:编程计算2^3。

assume cs:code
code segment
mov ax,2
add ax,ax
add ax,ax

mov ax,4c00h
int 21h
code ends
end

任务3:编程计算2^12

assume cs:code
code segment
mov ax,2
;做11次 add ax,ax

mov ax,4c00h
int 21h
code ends
end

按照我们的算法,计算 2^12 需要11条重复的指令 add ax,ax ,我们显然不想这样写程序,这里,可用 loop 来简化我们的程序。

程序5.1

assume cs:code
code segment
mov ax,2
mov cx,11
s: add ax,ax
loop s

mov ax,4c00h
int 21h
code ends
end

程序分析

标号:在汇编语言中,标号代表一个地址,程序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,循环次数
s:
循环执行的程序段
loop s

练习:用加法计算 123*236,结果存在 ax 中。

assume cs:code
code segment
mov ax,0
mov cx,236
s: add ax,123
loop s

mov ax,4c00h
int 21h
code ends
end

该程序执行了236次+123,其实执行123次+236会比上面的程序更好。

在Debug中跟踪用loop指令实现的循环程序

问题:计算 ffff:0006 单元中的数乘以3,结果存储在 dx 中。

程序

assume cs:code
code segment
mov ax,0ffffh ;这里注意:汇编中数据不能以字母开头,编译器会报错,前面一定要加0
mov ds,ax
mov bx,6 ;以上,设置ds:bx指向ffff:6

mov al,[bx]
mov ah,0 ;以上,设置(al) = ( (ds)*16 + (bx) ),(ah) = 0

mov dx,0 ;累加寄存器清0

mov cx,3 ;循环3次
s:add dx,ax
loop s ;以上累加计算(ax)*3

mov ax,4c00h
int 21h ;程序返回
code ends
end

Debug跟踪(部分)

初始化累加寄存器和循环计数寄存器

这两条指令执行后,(dx) = 0,完成对累加寄存器的初始化;(cx) = 3,完成了对循环计数寄存器的初始化。下面开始循环程序段的执行。

第1次循环

这是第一次循环。

CPU执行 loop 0012 ,第一步先将 (cx) 减1,(cx)=2,第二步因 (cx) 不等于0,将 IP 设为 0012h。

接着,重复执行 add dx,axloop 0012 ,直到 (cx) = 0为止。

循环

在完成最后一次 add dx,ax 后,(dx) = 93h,此时 dx 中为累加计算 (ax)*3 的最后结果。

假设标号 s 前的指令,已经确定在逻辑上完全正确,我们不想再一步步跟踪,只想跟踪循环的过程,可以使用Debug命令 g 一次执行完标号 s 前的指令。

循环程序段从 CS:0012 开始, g CS:0012 表示执行程序到当前代码段(段地址在CS中)的0012h处。

g

Debug执行 g 0012 后,CS:0012 前的程序段被执行,从各个相关的寄存器中的值,我们可以看出执行的结果。

那么,如果我们希望循环一次执行完,可以使用 p 命令来执行,Debug就会自动重复执行循环中的指令,直到 (cx)=0 为止。

p

在遇到 loop 时,用 p 命令执行,Debug自动重复执行 loop 0012add dx,ax 两条指令,直到 (cx)=0。

当然也可以使用 g 0016 直接执行到 CS:0016 处来达到目的。

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

Debug 和 masm对 mov al,[0] 等指令有不同的解释

Debug对 mov al,[0] 等指令的解释

Debug的解释

masm对 mov al,[0] 等指令的解释

masm的解释

Debug的解释:[idata] 是一个内存单元,idata 是内存单元的偏移地址;
编译器的解释:[idata] 是 idata。

如何在源程序中实现将内存 2000:0、2000:1、2000:2、2000:3 单元中的数据送入 al,bl,cl,dl中?

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

image-20200803114414671

总结:

  • 在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须用 [...] 来表示内存单元,如果在 [] 里用一个常量 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
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0 ;初始化 ds:bx 指向ffff:0

mov dx,0 ;初始化累加寄存器,(dx)=0

mov cx,12 ;初始化循环计数寄存器cx,(cx) = 12

s: mov al,ds:[bx]
mov ah,0 ;(ax) = ((ds) * 16 + (bx))
add dx,ax ;间接向dx中加上 ((ds) * 16 + (bx))单元的数值
inc bx ;ds:bx 指向下一个单元
loop s

mov ax,4c00h
int 21h

code ends
end

段前缀

mov ax,ds:[bx]
mov ax,cs:[bx]
mov ax,ss:[bx]
mov ax,es:[bx]
mov ax,ss:[0]
mov ax,cs:[0]

这些访问内存单元的指令中,显式指明内存单元段地址的 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
code segment
mov ax,0ffffh
mov ds,ax ;(ds) = 0ffffh

mov ax,0020h
mov es,ax ;(es) = 0020h

mov bx,0 ;(bx) = 0,此时 ds:bx 指向 ffff:0,es:bx指向 0020:0

mov cx,12 ;(cx) = 12,循环12次

s: mov dl,[bx] ;(dl) = ((ds) *16 + (bx)),将 ffff:bx 中的数据送入dl
mov es:[bx],dl ;((es) *16 + (bx))= (dl),将dl中的数据送入 0020:bx
inc bx ;(bx) = (bx) + l
loop s

mov ax,4c00h
int 21h
code ends
code

实验4 [bx]和loop的使用

编程,向内存 0:200~0:23F 依次传送数据 0~63(3FH),程序中只能使用9条指令,9条指令中包括 mov ax,4c00hint 21h

assume cs:code
code segment
mov ax,0020h
mov ds,ax

mov bx,0
mov cx,64

s: mov [bx],bx
inc bx
loop s

mov ax,4c00h
int 21h
code ends
end