目录
一、[bx]与loop基本描述
[bx]也表示一个内存单元,它的偏移地址在bx中,如下指令:
mov ax, [bx]
将一个内存单元中的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中
之后为了描述上的见解,我们用()来表示一个寄存器或是一个内存单元中的内容,比如(ax)表示ax中的内容,(al)表示al中的内容,(20000H)表示内存20000单元的内容
可以看看(X)的应用:
- ax中的内容是0010H,可以描述为(ax)= 0010H
- 2000:1000处的内容为0010H,可以描述为(21000H)= 0010H
- mov ax, [2]的功能,可以描述为:(ax)= ((ds)* 10H + 2)
- add ax, 2的功能:(ax)= (ax)+ 2
- push ax的功能:(sp)= (sp)- 2,((ss)*10H+(SP))
(al)、(bl)等所得到的数据为字节型,(ds)、(ax)等得到的数据是字型
之后用idata表示常量
mov ax, [idata]代表:mov ax, [1]、mov ax, [2]、mov ax, [3]
二、[BX]
mov ax, [bx]
功能:bx中存放的数据作为一个偏移地址EA,段地址默认在ds中,将SA:EA处的数据送入ax中,即(ax)= ((ds)*16 + (bx))
mov [bx], ax
功能:bx中存放的数据作为一个偏移地址EA,段地址默认在ds中,将ax的数据送入内存SA:EA中,即((ds)*16 + (bx))= (ax)
程序和内存中的情况如下图所示,写出程序执行后,21000H ~ 21007H单元中的内容
inc bx的含义是bx中的内容加1
mov bx, 1
inc bx
执行后bx = 2
分析:
- 先看程序的前三条指令:
mov ax, 2000H
mov ds, ax
mov bx, 1000H
结束后,ds = 2000H,bx = 1000H
- 接下来,第4条指令:
mov ax, [bx]
将内存2000:1000处的字型数据送入ax中,执行后ax = 00beH
- 接下来,第5、6条指令
inc bx
inc bx
执行前bx = 1000H,执行后,bx = 1002H
- 接下来,第7条指令
mov [bx], ax
执行后
- 接下来,第8、9条指令
inc bx
inc bx
执行结束,bx = 1004H
- 接下来,第10条指令
mov [bx], ax
- 接下来,第11条指令
inc bx
此时bx = 1005H
- 接下来,第12条指令
mov [bx], al
al中的数据送入
接下来几条指令同理,最终结果如图所示
三、Loop指令
loop指令的格式是:loop 标号,cpu执行到loop指令的时候,要进行两步操作:
- (cx)= (cx)- 1;
- 判断cx中的值,不为0则跳转至标号处执行程序,为0则向下执行
可以看出,cx中的值影响着loop指令的执行结果,通常用loop指令来执行循环功能,cx中存放循环次数,下面通过一个具体的程序来看一下loop指令的具体应用
1. 计算2^2,结果存在ax中
分析:设(ax)= 2,可以计算(ax)= (ax)* 2,最后(ax)中的结果为2^2的值,N*2可用N + N来实现
assume cs:code
code segment
mov ax, 2
add ax, ax
mov ax, 4c00H
int 21H
code ends
end
2. 计算2^3,结果存在ax中
分析:2 ^ 3 = 2 * 2 * 2, 设(ax)= 2,可以计算(ax)= (ax)* 2 * 2,最后(ax)中的结果为2^3的值,N*2可用N + N来实现
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,结果存在ax中
分析:2 ^ 12 = 2 * 2 * 2 * 2… * 2, 设(ax)= 2,可以计算(ax)= (ax)* 2 * 2 * … * 2,最后(ax)中的结果为2^12的值,N*2可用N + N来实现
assume cs:code
code segment
mov ax, 2
; 做11次 add ax, ax
mov ax, 4c00H
int 21H
code ends
end
显然是有11条重复的指令add ax,ax。我们显然不希望这样子来编写程序,我们可以用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
分析:
-
标号
在汇编语言中,标号代表一个地址,程序中有一个标号s,他标示了一个地址,这个地址处有一条指令:add ax, ax
-
loop s
CPU在执行loop s
的时候,需要进行两步操作
(a)(cx)= (cx)- 1;
(b)判断cx中的值,不为0则转至标号s所标示的地址处执行,如果为0执行下一条指令(mov ax,4c00h) -
以下三条指令
mov cx, 11
s: add ax, ax
loop s
执行loop s时,先将(cx)减1,然后若(cx)不为0,转至s处执行add ax,ax。所以可以利用cx来执行add ax,ax的执行次数
用cx和loop指令相配合实现循环的功能框架如下:
mov cx, 循环次数
s:
循环执行的程序段
loop s
编程:用加法计算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
改进:上述程序循环了236次加法,我们可以将236加123次,这样子效率就提高了许多
四、在Debug中跟踪用loop指令实现的循环程序
考虑这样一个问题:计算ffff:0006单元中的数乘以3,结果存储在dx中
我们分析一下:
- 运算后的结果是否会超出dx所能存储的范围?
ffff:0006单元中的数是一个字节型的数据,范围在0 ~ 255之间,则用它和3相乘的结果不会大于65535,可以在dx中存放下
- 用循环累加来实现乘法,用哪一个寄存器来进行累加?
将ffff:0006单元中的数赋值给ax,用dx进行累加。先设(dx)= 0,然后做3次dx = (dx)+ (ax)
- ffff:6单元是一个字节单元,ax是一个16位寄存器,数据的长度不一样,如何赋值?
ffff:6是一个字节单元,单元中的数据为XXH,若要让ax中的值和ffff:6单元中的值相等,则应当让ax中的数据为00XXH,所以若要实现ffff:0006单元向ax赋值,应该令(ah)= 0,(al)= (ffff6H)
程序如下
assume cs:code
code segment
mov ax, 0ffffh
mov ds, ax
mov bx, 6 ;设置ds:bx指向ffff:6
mov al, [bx]
mov ah, 0 ; 设置(al)= ((ds*16)+(bx))
mov dx, 0
mov cx, 3 ; 循环3次
s: add dx, ax
loop s
mov ax, 4c00h
int 21h
code ends
end
这里程序的第一条指令mov ax, 0ffffh
是因为在汇编源程序中,数据不能以字母开头,所以要在前面加0
接下来我们对程序的执行过程进行跟踪:
这里我们是要debug 它的exe程序
这里(ds)= 075A,所以程序在076A:0处。(cs)= 076A,(IP)= 0,CS:IP指向程序的第一条指令,再用u命令查看被Debug加载入内存的程序,如图所示:
接下来我们开始跟踪:
前三条指令执行后,(ds)= FFFFH,(bx)= 6,ds:bx指向ffff:6单元。
最后一个-t,表明当前要读取的指令是mov al, [bx]
,因为是读取内存的指令,所以debug将要访问的内存单元中的内容也显示出来,可以看到屏幕最右边显示的是"ds:0006 = 32"由此我们可以知道此时目标单元(ffff6)的内容是31h
执行后,(ax) = 0031h,完成了赋值
…接着-t指令,就可以执行完所有
拓展:我们将单元中的数乘以123,结果存储在dx中
我们需要将循环改为123次就可以
assume cs:code
code segment
mov ax, 0ffffh
mov ds, ax
mov bx, 6 ;设置ds:bx指向ffff:6
mov al, [bx]
mov ah, 0 ; 设置(al)= ((ds*16)+(bx))
mov dx, 0
mov cx, 123 ; 循环123次
s: add dx, ax
loop s
mov ax, 4c00h
int 21h
code ends
end
我们在这里不想再一步一步的进行跟踪了,只想跟踪循环过程,希望可以一次执行标号s前的指令,可以用一个新指令:g命令达到目的。
我们想让程序从CS:0012处开始跟踪,我们就可以使用g命令,执行g 0012
后,CS:0012前的程序段被指向
如果我们想让循环一次执行完,可以使用p命令达到目的,再次遇到loop指令的时候,使用p命令来执行,debug就将自动重复执行循环中的指令,直到(cx)= 0为止。
也可以使用g命令跳转至mov ax, 4c00h
处
五、Debug和汇编编译器masm对指令的不同处理
我们在Debug中写过类似指令:
mov ax, [0]
表示将ds:0处的数据送入ax中
但是在汇编源程序中,指令mov ax, [0]
会被编译器当作指令mov ax, 0
处理
下面通过具体例子来看Debug和汇编编译器masm对这类指令的不同处理:
将内存2000:0、2000:1、2000:2、2000:3单元中的数据送入al,bl,cl,dl中
(1)在Debug中编程实现:
(2)将汇编源程序存储为.asm,用masm和link生成.exe,用Debug加载.exe
可以看到:Debug将[idata]看为是一个内存单元,idata是偏移地址,而编译器直接将[idata]视为idata
因此mov al, [0]
的含义是(al)= 0,将常量0送给al,即al = 0(与mov al, 0含义相同)
总结:
-
在汇编源程序中,如果指令访问一个内存单元,则在指令中必须用[…]来表示一个内存单元,如果在[]里用一个常量idata直接给出内存单元的偏移地址,就要在[]的前面显示的给出段地址所在的段寄存器
比如:mov al, ds:[0]
(含义是(al)=((ds)*16 + 0)将内存单元中的数据送入al)
如果没有在[]前面显示给出段地址所在的段寄存器,比如
mov al, [0]
编译器就将他视为mov al, 0
-
如果在[]里面用寄存器,比如bx,则是间接给出内存单元的偏移地址,则段地址默认在ds中,当然也可以显示给出段地址所在的段寄存器
六、loop和[bx]的联合使用
计算ffff:0 ~ ffff:b单元中的数据和,结果存储在dx中
分析:
- 运算后的结果是否会超出dx所能存储的范围?
ffff:0 ~ ffff:b单元中的数是一个字节型的数据,范围在0 ~ 255之间,12个数相加的结果不会大于65535,可以在dx中存放下
- 用循环累加来实现乘法,用哪一个寄存器来进行累加?
不行,因为ffff:0 ~ ffff:b中的数据是8位的,不能直接加到16位寄存器dx
- 怎么将ffff:0 ~ ffff:b中的8位数据累加到16位寄存器中去?
目前计算用一个16位寄存器作为中介。将内存单元中的8位数据赋值到一个16位寄存器ax,再将ax中的数据加到dx上去,从而使两个运算对象的类型匹配而且结果不会越界
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
s: mov al,[bx] ;ffff:0处的字节内容送给al
mov ah,0
add dx,ax
inc bx ;ds:bx指向下一个单元
loop s
mov ax,4c00h
int 21h
code ends
end
七、段前缀
指令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称为段前缀
八、一段安全的空间
随意向一段内存空间写入内容是很危险的,因为这段空间中可能存放着重要的系统数据或代码:
mov ax,1000h
mov ds,ax
mov al,0
mov ds:[0],al
这里如果1000:0处存放着重要的系统数据或代码,mov ds:[0],al
将其改写将引发错误。
在DOS方式下,一般情况,0:200 ~ 0:2ff空间中没有系统或其他程序的数据或代码,以后我们就使用这段空间
九、段前缀的使用
将内存ffff:0 ~ ffff:b单元中的数据复制到0:200 ~ 0:20b单元中去
0:200 ~ 0:20b单元等同于0020:0 ~ 0020:b单元,他们描述的是同一段内存空间
思路:
-
初始化x = 0,循环十二次,将ffff:x单元中的数据送入0020:x(需要用一个寄存器中转)x = x + 1
-
在循环中,ffff:X和目标单元0020:X中的偏移地址X是变量,我们使用bx来存放
程序如下:
assume cs:code
code segment
mov bx,0
mov cx,12
s: mov ax,0ffffh
mov ds,ax
mov dl,[bx] ;将ffff:bx中的数据送入dl
mov ax,0020h
mov ds,ax ;ds = 0020h
mov [bx],dl ;将dl中的数据送入0020:bx
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
在这里我们可以进行改进,程序中我们设置了两个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 ;此时ds:bx指向ffff:0,es:bx指向0020:0
mov cx,12
s: mov dl,[bx] ;将ffff:bx中的数据送入dl
mov es:[bx],dl ;将dl中的数据送入0020:bx
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end