第五章 [bx]和loop指令
[bx]是什么呢?
和[0]有些类似,[0]表示内存单元,它的偏移地址是0
我们要完整地描述一个内存单元,需要两种信息:
- 内存单元的地址;
- 内存单元的长度(类型)。
我们用[0]表示一个内存单元时,0 表示单元的偏移地址,段地址默认在ds中,单元的长度(类型)可以由具体指令中的其他操作对象(比如说寄存器)指出。
[bx]同样也表示一个内存单元,它的偏移地址在bx中,比如下面的指令:
mov ax,[bx]
mov al,[bx]
描述性符号()
使用一个描述性的符号 “() ”来表示一个寄存器或一个内存单元中的内容
应用:
(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
约定符号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等。
5.2 loop指令
指令的格式是:loop 标号,CPU 执行loop指令的时候,要进行两步操作:
- (cx)=(cx)-1;
- 判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。
任务:编程计算2∧2,结果存放在ax中
assume cs:code
code segment
mov ax,2
add ax,ax
mov ax,4c00h
int 21h
code ends
end
任务:编程计算2∧12。
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
loop总结:
- 在cx中存放循环次数;
- loop 指令中的标号所标识地址要在前面;
- 要循环执行的程序段,要写在标号和loop 指令的中间。
程序框架
mov cx,循环次数
s:循环执行的程序段
loop s
5.3 在Debug中跟踪用loop指令实现的循环程序
问题:计算ffff:0006单元中的数乘以3,结果存储在dx中
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,6
mov al,[bx]
mov ah,0
mov dx,0
mov cx,3
s: add dx,ax
loop s
mov ax,4c00h
int 21h
code ends
end
PS:
注意程序中的第一条指令mov ax,0ffffh。
我们知道大于9FFFH的十六进制数据A000H、A001H、…… 、C000H、C001H、……、FFFEH、FFFFH等,在书写的时候都是以字母开头的。
而在汇编源程序中,数据不能以字母开头,所以要在前面加0。
在Dedug时候遇到loop指令时,使用P命令来执行。Debug就会自动重复执行循环中的指令,直到(cx)=0为止。
注意:
在汇编元程序中,指令“mov ax,[0]”被编译器当作指令“mov ax,0”处理。
在Debug中的指令:mov ax,[0],表示将ds:0处的数据送入al中。
实例:
问题:将内存2000:0、2000:1 、2000:2、2000:3单元中的数据送入al,bl,cl,dl中。
在Debug中编程实现:
mov ax,2000h
mov ds,ax
mov al,[0]
mov bl,[1]
mov cl,[2]
mov dl,[3]
结果:
mov al,[0000]
mov bl,[0001]
mov cl,[0002]
mov dl,[0003]
汇编程序实现:
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
结果:
mov al, 00
mov bl, 01
mov cl, 02
mov dl, 03
5.5 loop和[bx]的联合应用
问题:计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中
分析:
1.运算后的结果是否会超出 dx 所能存储的范围?
ffff:0~ffff:b内存单元中的数据是字节型数据,范围在0~255之间,12个这样的数据相加,结果不会大于 65535 ,可以在dx中存放下。
2.我们是否将 ffff:0~ffff:b中的数据直接累加到dx中?
当然不行,因为ffff:0~ffff:b中的数据是8位的,不能直接加到16位寄存器dx中。
3.我们能否将ffff:0~ffff:b中的数据累加到dl中,并设置(dh=0,从而实现累加到dx中的目标?
这也不行,因为dl是8位寄存器,能容纳的数据的范围在小 255 之间,ffff : 0~ffff:b中的数据也都是 8 位,如果仅向dl中累加12个 8 位数据,很有可能造成进位丢失。
4.我们到底怎样将用ffff:0~ffff:b中的8位数据,累加到16位寄存器dx中?
- 从上面的分析中,我们可以看到,这里面有两个问题:类型的匹配和结果的不超界。
- 具体的说,就是在做加法的时候,我们有两种方法:
(dx)=(dx)+内存中的8位数据:
(dl)=(dl)+内存中的8 位数据; - 第一种方法中的问题是两个运算对象的类型不匹配,第二种方法中的问题是结果有可能超界。
目前的方法:
我们将内存单元中的 8 位数据赋值到一个16位寄存器ax中,再将ax中的数据加到dx上,从而使两个运算对象的类型匹配并且结果不会超界。
(ds)=0ffffh
(bx)=0
(dx)=0
(cx)=12
循环12 次:
s:(al)=((ds)*16+(bx))
(ah)=0
(dx)=(dx)+(ax)
(bx)=(bx)+1
loops
在实际编程中,经常会遇到,用同一种方法处理地址连续的内存单元中的数据的问题。
我们需要用循环来解决这类问题,同时我们必须能够在每次循环的时候按照同一种方法来改变要访问的内存单元的地址。
这时,我们就不能用常量来给出内存单元的地址(比如[0]、[1]、[2]中,0、1、2是常量),而应用变量。
“mov al,[bx]”中的 bx就可以看作一个代表内存单元地址的变量,我们可以不写新的指令,仅通过改变bx中的数值,改变指令访问的内存单元。
5.6 段前缀
指令“mov ax,[bx]”中,内存单元的偏移地址由bx给出,而段地址默认在ds中。
我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器。
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:”、“cs:”、“ss:”或“es:”,在汇编语言中称为段前缀。
5.7 一段安全的空间
在8086模式中,随意向一段内存空间写入内容是很危险的 ,因为这段空间中可能存放着重要的系统数据或代码。
注意:
我们在纯DOS方式(实模式)下,可以不理会DOS,直接用汇编语言去操作真实的硬件,因为运行在CPU实模式下的DOS,没有能力对硬件系统进行全面、严格地管理。
但在Windows 2000、UNIX这些运行于CPU保护模式下的操作系统中,不理会操作系统,用汇编语言去操作真实的硬件,是根本不可能的。硬件已被这些操作系统利用CPU保护模式所提供的功能全面而严格地管理了。
在一般的PC机中,DOS方式下,DOS和其他合法的程序一般都不会使用0:200~0:2FF( 0:200h~0:2FFh)的256 个字节的空间。所以,我们使用这段空间是安全的。
5.8 段前缀的使用
问题:将内存ffff:0~ffff:b段元中的数据拷贝到 0:200~0:20b单元中
分析:
0:2000:20b单元等同于0020:00020:b单元,它们描述的是同一段内存空间:
拷贝的过程应用循环实现,简要描述如下:
初始化:X=0 循环12次: 将ffff:X单元中的数据送入0020:X(需要用一个寄存器中转) X=X+1
- 在循环中,源单元ffff:X和目标单元的0020:X的偏移地址X是变量。我们用bx来存放。
- 我们用将0:2000:20b用0020:00020:b描述,就是为了使目标单元的偏移地址和源始单元的偏移地址从同一数值0开始。
代码:
assume cs:code
code segment
mov bx,0
mov cs,12
s: mov ax,offffh
mov ds,ax ;ds为段地址
mov dl,[bx] ;dl为暂存地址
mov ax,0020h
mov ds,ax
mov [bx],dl
inc bx ;(bx)=(bx)+1
loop s
mov ax,4c00h
int 21h
code ends
end
分析:
因源单元ffff:X和目标单元0020:X 相距大于64KB,在不同的64KB段里,程序中,每次循环要设置两次ds。
这样做是正确的,但是效率不高。
我们可以使用两个段寄存器分别存放源单元ffff:X和目标单元0020:X的段地址,这样就可以省略循环中需要重复做12次的设置ds的程序段。
优化代码:
assume cs:code
code segment
mov ax,offffh
mov ds,ax
mov ax,0020h
mov es,ax
mov bx,0
mov cx,12
s: mov dl,[bx]
mov es:[bx],dl
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
省去了重复设置ds的步骤