1、[bx]和内存单元的描述
[0]表示内存单元,它的偏移地址是0。比如在Debug中使用,如下指令:
mov ax, [0]
代表:将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为0,段地址在ds中。
mov al, [0]
代表:将一个内存单元的内容送入al,这个内存单元的长度为1个字节(字节单元),存放一个字节,偏移地址为0,段地址在ds中。
我们要完整地描述一个内存单元,需要两种信息:
(1)内存单元的地址;
(2)内存单元的长度(类型)。
我们用[0]表示一个内存单元时,0 表示单元的偏移地址,段地址默认在ds中,单元的长度(类型)可以由具体指令中的其他操作对象(比如说寄存器)指出,如前边的AX,AL。
[bx]同样也表示一个内存单元,它的偏移地址在bx中,比如下面的指令:
mov ax, [bx]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。
mov al, [bx]
将一个内存单元的内容送入al,这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址在bx中,段地址在ds中。
描述性符号 ()
为了描述上的简洁,在以后的课程中,我们将使用一个描述性的符号 “() ”来表示一个寄存器或一个内存单元中的内容。
比如:(ax)表示ax中的内容、(al)表示al中的内容;
(20000H)表示内存20000H单元的内容;(()中的内存单元的地址为物理地址。)
((ds)*16+(bx))表示:ds中的内容为ADR1,bx中的内容为ADR2,内存ADR1*16+ADR2单元的内容也可以理解为:ds中的ADR1作为段地址,bx中的ADR2作为偏移地址,内存ADR1:ADR2单元的内容。
注意:
“()”中的元素可以有三种类型:寄存器名、段寄存器名、内存单元的物理地址(一个20位数据)。比如:
(ax)、(ds)、(al)、(cx)、(20000H)、((ds)*16+(bx))等是正确的用法;
(2000H)、(2000:0)、((ds):1000H) 等是不正确的用法;
我们看一下(X)的应用,比如:
(1)ax中的内容为0010H,我们可以这样来描述:(ax)=0010H;
(2)2000:1000 处的内容为0010H,我们可以这样来描述:(21000H)=0010H;
(3)对于mov ax,[2]的功能,我们可以这样来描述:(ax)=((ds)*16+2);
(4)对于mov [2],ax 的功能,我们可以这样来描述:((ds)*16+2)=(ax);
(5)对于 add ax,2 的功能,我们可以这样来描述:(ax)=(ax)+2;
(6)对于add ax,bx的功能,我们可以这样来描述:(ax)=(ax)+(bx);
(7)对于push ax的功能,我们可以这样来描述:
- (sp) = (sp)-2
- ((ss)*16+(sp))=(ax)
(8)对于pop ax 的功能,我们可以这样来描述:
- (ax)=((ss)*16+(sp))
- (sp)=(sp)+2
“(X)”所表示的数据有两种类型:字节、字。是哪种类型由寄存器名或具体的运算决定,比如:
(al)、(bl)、(cl)等得到的数据为字节型;
(ds)、(ax)、(bx)等得到的数据为字型。
约定符号 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,l、mov bx,2、mov bx,3等。
- mov ds,idata就代表mov ds,1、mov ds,2等,(是非法指令,段寄存器是需要中转的)
2、[bx]
我们看一看下面指令的功能:
mov ax,[bx]
功能:bx 中存放的数据作为一个偏移地址EA ,段地址SA 默认在ds 中,将SA:EA处的数据送入ax中。
即: (ax)=(ds *16 +(bx));
mov [bx],ax
功能:bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将ax中的数据送入内存SA:EA处。
即:(ds *16 +(bx)) = (ax)
问题5.1 :
程序和内存中的情况如下图所示,写出程序执行后,21000H~21007H 单元中的内容。
注意,inc bx的含义是bx中的内容加1,比如下面两条指令:
mov bx,1
inc bx
执行后,bx=2。
分析:
3、Loop指令
loop 指令的格式是:loop 标号,CPU执行loop指令的时候,要进行两步操作:
- (cx)=(cx)-1;
- 判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。;总体而言,类似于do……while ,先执行一次,再进行判断。
从上面的描述中,我们可以看到,cx中的值影响着loop指令的执行结果。通常(注意,我们说的是通常)我们用loop指令来实现循环功能,cx 中存放循环次数。
编程任务1:编程计算2^3:
assume cs:code
code segment
mov ax,2
add ax,ax
add ax,ax
mov ax,4c00H
int 21H
code ends
end
便程任务2:编程计算2^12
分析:首先将ax初值设为2;然后2^12=ax*2*2*2*2*2*2*2*2*2*2*2; N*2可用N+N实现,程序如下:
assume cs:code
code segment
mov ax,2
做11次 add ax,ax
mov ax,4c00H
int 21H
code ends
end
采用loop指令,重写程序:
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
程序分析:
(1)标号
在汇编语言中,标号代表一个地址,此程序中有一个标号s 。它实际上标识了一个地址,这个地址处有一条指令:add ax,ax。
(2)loop s
CPU 执行loop s的时候,要进行两步操作:
① (cx)=(cx)-1;
② 判断cx 中的值,不为0 则转至标号s 所标识的地址处执行(这里的指令是“add ax,ax“),如果为零则执行下一条指令(下一条指令是mov ax,4c00h)。
(3)以下三条指令
mov cx,11
s: ddd ax,ax
loop s
执行loop s时,首先要将(cx)减1,然后若(cx)不为0,则向前转至s处执行add ax,ax。所以,我们可以利用cx来控制add ax,ax的执行次数。
从上面的过程中,我们可以总结出用cx和loop指令相配合实现循环功能的三个要点:
- 在cx中存放循环次数;
- loop指令中的标号所标识地址要在前面;
- 要循环执行的程序段,要卸载标号和loop指令的中间。
用cx和loop指令相配合实现循环功能的程序框架如下:
mov cx, 循环次数
s:
循环执行的程序段
loop s
问题 5.2
编程,用加法计算 123*236,结果存在ax中。思考后看分析:
分析:可用循环完成,将123加236次。可先设(ax)=0,然后循环做236次(ax)=(ax)+123。
程序如下:
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
问题5.3
改进程序5.2,提高123*236的计算速度。思考后看分析。
分析:
程序5.2 做了236次加法,我们可以将236加123次。可先设(ax)=0,然后循环做123次(ax)=(ax)+236,这样可以用123次加法实现相同的功能。
小结:
(1)【bx】的作用:作为偏移地址与DS配合
在Debug中写过类似的指令:
- mov ax,[0]
- 表示将ds:0处的数据送入al中。
但是在汇编源程序中,指令“mov ax,[0]”被编译器当作指令“mov ax,0”处理。
示例任务:将内存2000:0、2000:1 、2000:2、2000:3单元中的数据送入al,bl,cl,dl中。
- 在Debug中编程实现
- 汇编程序实现
Debug编程如下所示:
mov ax,2000h
mov ds,ax
mov al,[0]
mov bl,[1]
mov cl,[2]
mov dl,[3]
汇编源程序如下所示:
assume cs:code
code segment
mov ax,2000h
mov ds,ax
mov al,[0]
mov bl,[1]
mov cl,[2]
mov dl,[3]
mov ax,4c00h
int 21h
code ends
end
在MASM中mov ax, [2]是解释为mov ax,2的。一般我们是通过BX来代替,像这道题我们先mov bx, 2 再通过mov ax, [bx]来实现。
即:mov a1,[0] 改为:mov bx,2 ; mov ax, [bx]
如下指令:
如果在汇编程序/masm中:
mov al, [0] 表示:(al)=0
mov al, ds:[0] 表示:段地址在ds中,偏移地址为0的内存,内存中的内容给al
mov al, [bx] 表示:从默认的段地址(ds的定义在此语句的前面),偏移地址为bx处,内存单元中的内容给al
mov al, ds:[bx] 表示:将段地址为ds,偏移地址为bx处,内存单元中的内容给al
(2)loop 和 cx 合作
loop是循环,cx存放的是循环的次数
(3)debug 的-g 偏移地址 命令 和 -p命令
4、在Debug中跟踪用loop指令实现的循环程序
5、loop和[bx]的联合应用
6、段前缀
指令 “mov ax,[bx]”中,内存单元的偏移地址由bx给出,而段地址默认在ds中。我们可以在访问内存单元的指令中显式地给出内存单元地段地址所在地段寄存器。比如:
mov ax,ds:[bx]
将一个内存单元地内容送入ax,这个内存单元地长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。
mov ax, cs:[bx]
将一个内存单元地内容送入ax,这个内存单元地长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在cs中。
mov ax, ss:[bx]
将一个内存单元地内容送入ax,这个内存单元地长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ss中。
mov ax, es:[bx]
将一个内存单元地内容送入ax,这个内存单元地长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在es中。
mov ax, ss:[0]
将一个内存单元地内容送入ax,这个内存单元地长度为2字节(字单元),存放一个字,偏移地址为0,段地址在ss中。
mov ax, cs:[0]
将一个内存单元地内容送入ax,这个内存单元地长度为2字节(字单元),存放一个字,偏移地址为0,段地址在cs中。
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:”、“cs:”、“ss:”、“es:”,在汇编语言中称为段前缀。
7、一段安全的空间
总结:
(1)我们需要直接向一段内存中写入内容;
(2)这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作很可能引发错误。
(3)DOS方式下,一般情况, 0:200~0:2FF 空间中没有系统或其他程序的数据或代码;
(4)以后,我们需要直接向一段内存中写入内容时,就使用0:200~0:2FF这段空间。
8、段前缀的使用
问题:将内存 ffff:0 - ffff:b 单元中的数据拷贝到 0:200 - 0:20b单元中。
分析:
(1) 0:200~0:20b单元等同于0020:0~0020:b单元,它们描述的是同一段内存空间:
(2)拷贝的过程应用循环实现,简要描述如下:
- 初始化:X=0
- 循环12次:
- 将ffff:X单元中的数据送入0020:X(需要用一个寄存器中转)
- X=X+1
(3)在循环中,源单元 ffff:X 和目标单元的 0020:X 的偏移地址 X 是变量。我们用 bx 来存放。
(4)我们用将 0:200~0:20b 用 0020:0~0020:b 描述,就是为了使目标单元的偏移地址和源始单元的偏移地址从同一数值0开始。
程序如下:
assume cs:code
code segment
mov bx,0 ;(bx)=0,偏移地址从0开始
mov cx,12 ;(cx)=12,循环12次
s: mov ax,0ffffh
mov ds,ax ;(ds)=0ffffh
mov dl,[bx] ;(dl)=((ds)*16+(bx)),将ffff:bx中的数据送入dl
mov ax,0020h
mov ds,ax ;(ds)=0020h
mov [bx],dl ;((ds)*16+(bx))=(dl),将dl的数据送入0020:bx
inc bx
loop s
mov ax,4c00H
int 21H
code ends
ends
上述程序分析:
- 因源单元ffff:X和目标单元0020:X 相距大于64KB,在不同的64KB段里,程序中,每次循环要设置两次ds。
- 这样做是正确的,但是效率不高。
- 我们可以使用两个段寄存器分别存放源单元ffff:X和目标单元0020:X的段地址,这样就可以省略循环中需要重复做12次的设置ds的程序段。
改进程序如下:
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,偏移地址从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
loop s
mov ax,4c00H
int 21H
code ends
ends
程序分析:
- 改进的程序中,使用 es 存放目标空间0020:0~0020:b的段地址,用ds存放源空间ffff:0~ffff:b的段地址。
- 在访问内存单元的指令“mov es:[bx],al”中 ,显式地用段前缀 “es:” 给出单元的段地址,这样就不必在循环中重复设置ds。
实验 4 [bx]和loop的使用
1、编程,向内存 0:200 - 0:23F依次传送数据 0-63(3FH),程序中只能使用9条指令,9条指令中包括 “mov ax 4c00H” 和 “int 21H”。
代码如下:
assume cs:code
code segment
mov ax,0020H
mov ds,ax
mov bx,0
mov cx,40h ;或mov cx,64
s: mov [bx],bx
inc bx
loop s
mov ax,4c00H
int 21H
code ends
ends
2、下面程序的功能是将“mov ax, 4c00H” 之前的指令复制到内存 0:200处,补全程序。
提示:
- 复制的是什么?从哪里到哪里?
- 复制的是什么?有多少个字节?读者如何知道要复制的字节的数量?
答案:
assume cs:code
code segment
mov ax, code ;或mov ax, cs
mov ds,ax
mov ax,0020h
mov es,ax
mov bx,0
mov cx, 18h ;或mov cx, 17h ;或sub cx,5
s: mov al,[bx]
mov es:[bx],al
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
此题有多个答案,因为mov用在寄存器之间传送数据的指令是2个字节,用在寄存器和立即数之间是3个字节
答案1:mov ax,cs (占2个字节)
mov cx,17
答案2:mov ax,code (占3个字节)
mov cx,18
答案3:mov ax,cs 或mov ax,code
把mov cx, 改成 sub cx,5
(因为在载入程序时,cx保存程序的长度,减去5是为减去mov ax,4c00h和int 21h的长度)
此题的目的是:
1、理解CS和CODE的关联
2、理解CS保存程序的代码段,即“复制的是什么,从哪里到哪里”
3、理解CX在载入程序后保存程序的长度。
4、理解数据和代码对CPU来说是没区别的,只要CS:IP指向的就是代码