第五章 [bx]和loop指令
1. [bx]和内存单元的描述
mov ax,[0] 表示将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址为0,段地址在ds中。
mov al, [0] 表示将一个内存单元的内容送入al, 这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址为0,段地址在ds中。
我们要完整地描述一个内存单元,需要两种信息: (1)内存单元的地址; (2) 内存单元的长度(类型)。
[bx]同样也表示一个内存单元,它的偏移地址在bx中,如:
mov ax, [bx]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在[bx]中,段地址在ds中。
mov al, [bx]
将一个内存单元的内容送入al, 这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址在[bx]中,段地址在ds中。
3. 我们定义的描述性符号“()”
为了描述上的简洁,我们将使用一个描述性符号“()”来表示一个寄存器或一个内存单元中的内容。如:
(ax)表示ax中的内容、
(20000H) 表示内存20000H单元的内容;(括号中的内存单元地址为物理地址。)
( (ds)*16 + (bx) ) 表示:
ds中的内容为ADR1,bx中的内容为ADR2,内存ADR1*16 + ADR2 单元的内容也可理解为: ds中的ADR1作为段地址,bx中的ADR2作为偏移地址,内存ADR1:ADR2单元的内容。
注意:
“()”中的元素可以有三种类型: (1)寄存器名; (2)段寄存器名;(3)内存单元的物理地址(一个20位数据)。
(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;
“()”所表示的数据有两种类型: 字节 和 字 。是哪种类型由寄存器名和具体的运算决定,如:
(al)、(bl)、(cl) 等得到的数据为字节型; (ds)、(ax)、(bx)等得到的数据位字型。
(al) = (20000H) ,则(20000H)得到的数据为字节型; (ax) = (20000H) ,则(20000H)得到的数据位字型。
4. 约定符号idata表示常量
mov ax, [idata] 就代表 mov ax, [1] 、mov ax, [23] 等。
mov bx, idata 就代表 mov ax, 1、mov bx,2等
mov ds, idata 就代表 mov ds,1 、mov ds,2 等,它们都是非法指令。
5.1 [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.2 Loop指令
loop指令的格式是:loop 标号,CPU执行loop指令的时候,要进行两步的操作: (1) (cx) = (cx) -1 ; (2) 判断cx中的值,不为0则转至标号处执行程序,如果为0则向下执行。
通常(注意,是通常)我们用loop指令来实现循环功能,cx中存放循环次数。
执行loop 标号时,首先要将(cx)减1,然后若(cx)不为0,则向前转至标号处执行。
例子: 计算2的12次幂。
分析: 若(ax) = 2, (ax) = (ax) *2*2...*2 , 则2的12次幂,则得进行11次的 add ax, ax 相加
代码:
assume cs:code
code segment
mov ax,2
mov cx,11
flag: add ax,ax
loop flag
mov ax,4c00h
int 21h
code ends
end
总结得loop循环要点:
(1) 在cx中存放循环次数;
(2) loop 指令中的标号所标识地址要在前面;
(3) 要循环执行的程序段,要写在标号和loop指令中间。
框架如下:
mov cx, 循环次数
标号: 循环程序段
loop 标号
5.3 在debug跟踪用loop指令实现的循环程序
考虑一个问题,计算ffff:0006 单元中的数乘以3,结果储存在dx中。
分析:
(1) 运算后的结构是否会超出dx所能存储的范围
ffff:0006 单元,即一个单元中的数是一个字节型数据,范围在0~255,则用它和3相乘结果不会大于65535 = 2^16 。dx可以存放;
(2) 我们用循环累加来实现乘法,用哪个寄存器进行了累加?
我们将 ffff:0006 单元中的书赋值给 ax , 用 dx 进行累加。 先设(dx) = 0, 然后做3次 (dx) = (dx) + (ax)。
(3) ffff:0006单元是一个字节单元(8位),ax是一个16 位寄存器,数据长度不同,如何赋值?
注意,我们提到的是“赋值”,就是说,让ax中的数据值(数据大小)和 ffff:0006单元中的数据值(数据大小)相等。8位数据01H 和16位数据0001H数据长度不同,但它们的值是相等的。则得: 令(ah) = 0, (al) = (ffff6H);
程序:
assume cs:code
code segment
mov ax, 0ffffH
mov ds, ax
mov bx, 6
mov ah, 0
mov al , [bx]
mov dx,0
s: add dx, ax
loop s
mov ax,4c00H
int 21H
code ends
end
注意第一条指令 mov ax, 0ffffH,我们知道,大于9FFFH的十六进制数据A000H~FFFFH,书写时都是以字母开头的。而在汇编源程序中,数据不能以字母开头,所以要在前面加0。
5.4 Debug 和汇编编译器Masm对指令的不同处理
如下命令:
mov ax, [0]
在Debug中,表示将ds:0处的数据送入ax中。但在汇编源程序中,则被编译器当作指令“mov ax, 0 ”处理。
若想在汇编程序中表示 mov ax, [0] ,则可将偏移地址暂存在寄存器中,如:
mov bx, 0
mov ax, [bx]
结论:
(1) 我们在汇编程序中,如果用指令访问一个内存单元,则在指令中必须用“[……]” 来表示内存,如果在“[]”里用一个常量idata直接给出内存单元的偏移地址,就要在“[]”的前面显式地给出段地址所在的段寄存器。如:
mov al, ds:[0]
若没有在“[]”的前面显式地给出段地址所在的段寄存器,如:
mov al, [0]
那么,编译器masm将把指令中的“[idata]”解释为“idata”。
(2) 如果在“[]”里用寄存器,比如bx,间接给出内存单元的偏移地址,则段地址默认在ds中。当然,也可以显式地给出段地址所在的段寄存器。
5.5 loop 和 [bx] 的联合应用
如例: 计算ffff:0 ~ ffff:b 单元中的数据的和,结果储存在dx中。
分析:
代码:
assume cs:code
code segment
mov ax,0ffffH
mov ds,ax
mov bx,0
mov cx,000CH
mov dx,0
s: mov al,[bx]
mov ah,0
add dx,ax
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
在实际编程中,经常会遇到,用同一种方法处理地址连续的内存单元中的数据的问题。我们需要用循环来解决这类问题,同时我们必须能够在每次循环的时候按照同一种方法来改变要访问的内存单元的地址。如“mov al, [bx]”,然后累加bx中的内容即可。
5.6 段前缀
我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器。如:
(1) mov ax, ds:[bx]
(2) mov ax, cs:[bx]
(3) mov ax, ss:[bx]
(4) mov ax, es:[bx]
(5) mov ax, ss:[0]
(6) mov ax, cs:[0]
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:”“cs:”“ss:”“es:”,则称为段前缀。
5.7 一段安全的空间
我们似乎面临一种选择,是在操作系统中安全、汇聚地编程,还是自由、直接地用汇编语言去操作真实的硬件,了解那些早已被层层系统软件掩盖的真相?在大部分情况下,我们选择后者。
注意:
我们在纯DOS方式(实模式)下,可以不理会DOS,直接用汇编语言去操作真实的硬件,因为运行咋CPU实模式下的DOS,没有能力对硬件系统进行全面、严格地管理。但在Windows2000、UNIX这些运行与CPU保护模式下的操作系统中,不理会操作系统,用汇编语言去操作真实的硬件,是根本不可能的。硬件已被这些操作系统利用CPU保护模式所提供的功能全面而严格地管理了。
在一般的PC机中,DOS方式下,DOS和其他合法的程序一般都不会使用 0:200~0:300 的256个字节的空间。所以,我们使用这段空间是安全的。
总结:
(1) 我们需要直接向一段内存中写入内容;
(2) 这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作很可能引发错误;
(3) DOS方式下,一般情况, 0:200~0:300 空间中没有系统或其他程序的数据或代码;
(4) 以后,我们需要直接向一段内存中写入内容时,就使用0:200~0:300这段空间。
5.8 段前缀的使用
注意:当用Debug 命令调试.exe时,用P命令可达到执行完整个loop循环体的效果,而不用t命令单步执行循环。
例子: 向内存0:200~023F依次传送数据0~63(3FH),并把这段数据拷贝到0:240~0:300之间的末端安全区域。
我的代码:
assume cs:code
code segment
mov ax,0
mov ds,ax
mov bx,0
mov cx,64
s: mov [bx+200H],bx
inc bx
loop s
mov bx,200H
mov cx,64
f: mov dl,[bx]
mov [bx+0C0H],dl
inc bx
loop f
mov ax,4c00H
int 21H
code ends
end
数据拷贝到了0:2C0 ~ 0:2FF这段区域中 。
--------------------------------------------------------------------------------
实验四
(3) 将程序“mov ax,4c00h”之前的指令复制到内存0:200处 (原题给出的填空)
assume cs:code
code segment
mov ax,___
mov ds,ax
mov ax,0020h
mov es,ax
mov bx,0
mov cx,___
s:mov al,[bx]
mov es:[bx],al
inc bx
loop s
mov ax,4c00h
int 21h
code segment
end
分析:
从程序的开始处复制到“mov ax,4c00h”之前,ds 和 cs (code)寄存器存放了代码起始处的段地址,而代码的长度则存储在程序刚刚被加载进内存时CX寄存器中。 由于 mov ax,4c00h 和 int 21h 两条指令共占5个字节。所以所复制的字节单元数为: CX - 5 H 。
很多网上给出的答案,则是第一空填入code,第二空估计一个值填入,然后MASM 编译,LINK进行连接后,进入Debug 调试.exe,观察CX中的代码长度值,然后再减5,计算出结果后再重新填入正确的值到第二空。该方法有独到之处,也许是正是书作者的目的,但感觉也不是太好。
一个想法是把第二空整个改了: 改成 sub cx , 5H 具有一定得通用性。
总结:
程序被初始化加载进内存后,寄存器DS 和 CS中存放了程序起始的段地址; CX中存放了代码的长度值。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/syf442/archive/2009/08/11/4432665.aspx