[BX]和Loop指令
- 1 描述性符号: “()”
- 2 idata常量
- 3 [BX]
- 4 Loop
- 5 Debug和汇编编译器masm对指令的不同处理
- 6 Loop和[BX]的联合应用
- 7 段前缀
本文属于《 X86指令基础系列教程》之一,欢迎查看其它文章。
1 描述性符号: “()”
我们定义的描述性的符号: "()”,为了描述上的简洁,在以后的课程中,我们将使用一个描述性的符号 “()”来表示一个寄存器或一个内存单元中的内容。
比如:(ax)表示ax中的内容、(al)表示al中的内容
- (20000H)表示内存 20000H 单元的内容(0中的内存单元的地址为物理地址);
- ((ds)*16+(bx))表示:ds中的内容为ADR1, bx中的内容为ADR2,内存ADR1×16+ADR2单元的内容。也可以理解为: ds中的ADR1作为段地址, bx中的ADR2作为偏移地址,内存ADR1:ADR2 单元的内容。
注意, “()”中的元素可以有 3 种类型:①寄存器名;②段寄存器名; ③内存单元的物理地址(一个20位数据)。
比如:(ax)、(ds)、 (al)、(cx)、 (20000II)、((ds)*161(bx))等是正确的用法;(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)等得到的数据为字型。
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,1、mov bx,2、mov bx,3 等。
3 [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)。
4 Loop
loop指令的格式是: loop 标号。
CPU执行loop指令的时候,要进行两步操作:
- ①(cx)=(cx)-1;
- ②判断 cx 中的值,不为零则转至标号处执行程序,如果为零则向下执行。
我们用loop指令来实现循环功能, cx中存放循环次数。
以下 3 条指令:
mov cx,11
s: add ax,axloop s
执行loops时,首先要将(cx)减1,然后若(cx)不为0,则向前转至s处执行add ax,ax。所以,可以利用cx来控制add ax,ax的执行次数。
从上面的过程中,我们可以总结出用 cx 和 loop 指令相配合实现循环功能的 3 个要点:
- (1) 在 cx 中存放循环次数;
- (2) loop指令中的标号所标识地址要在前面;
- (3)要循环执行的程序段,要写在标号和loop指令的中间。
用cx和loop指令相配合实现循环功能的程序框架如下:
mov cx,循环次数
s: 循环执行的程序段loop s
5 Debug和汇编编译器masm对指令的不同处理
我们在Debug中写过类似的指令:
mov ax, [0]
表示将ds:0处的数据送入ax中。
但是在汇编源程序中,指令"mov ax,[0]”被编译器当作指令"mov ax,0"处理。
下面通过具体的例子来看一下Debug和汇编编译器masm对形如“mov ax,[0]”这类指令的不同处理。
任务:将内存2000:0、 2000:1、2000:2、2000:3单元中的数据送入al,b1,cl,d1中。
(1)在Debug 中编程实现:
mov ax,2000
mov ds,ax
mov al, [0]
mov bl, [1]
mov cl, [2]
mov dl, [3]
(2)汇编源程序实现:
assume cs:code
code segmentmov ax,2000h
mov ds,ax
mov al, [0]
mov bl, [1]
mov cl, [2]
mov dl, [3]mov ax,4c00h
int 21hcode ends
end
我们看一下两种实现的实际实施情况:
(1) Debug中的情况如下图所示。
(2)将汇编源程序存储为compare.asm,用masm、 link生成compare.exe,用Debug加载compare.exe,如下图所示。
我们在Debug中和源程序中写入同样形式的指令:“mov al,[0]”、“mov b1,[1]”、“mov cl,[2]” 、“mov d1,[3]” ,但Debug和编译器对这些指令中的"[idata]”却有不同的解释。
Debug将它解释为"[idata]”是一个内存单元,“idata”是内存单元的偏移地址;而编译器将"[idata]”解释为"idata"。
那么我们如何在源程序中实现将内存2000:0、 2000:1、 2000:2、2000:3单元中的数据送入 al,bl,cl,dl 中呢?
目前的方法是,可将偏移地址送入bx寄存器中,用[bx]的方式来访问内存单元。比如我们可以这样访问2000:0单元:
mov ax,2000h
mov ds,ax ;段地址 2000h 送入 ds
mov bx,0 ;偏移地址 0 送入 bx
mov al, [bx] ;ds:bx单元中的数据送入al
这样做是可以,可是比较麻烦,我们要用bx来间接地给出内存单元的偏移地址。我们还是希望能够像在Debug中那样,在"[]”中直接给出内存单元的偏移地址。这样做,在汇编源程序中也是可以的,只不过,要在“[]”的前面显式地给出段地址所在的段寄存器。比如我们可以这样访问2000:0单元:
mov ax,2000h
mov ds,ax
mov al,ds:[0]
比较一下汇编源程序中以下指令的含义。
- “mov al,[0]” ,含义: (al)=0,将常量0送入al中(与mov al,0含义相同);
- “mov al,ds:[0]” ,含义: (al)=((ds)*16+0),将内存单元中的数据送入al中;
- “mov al,[bx]” ,含义: (al)=((ds)*16+(bx)),将内存单元中的数据送入al中;
- "mov al,ds:[bx]” ,含义:与"mov al,[bx]”相同。
从上面的比较中可以看出:
(1)在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须用“[…]”来,表示内存单元,如果在"[]”里用一个常量idata直接给出内存单元的偏移地址,就要在“[]”的前面显式地给出段地址所在的段寄存器。比如:
mov al,ds: [0]
如果没有在“[]”的前面显式地给出段地址所在的段寄存器,比如:
mov al, [0]
那么,编译器masm将把指令中的"[idata]“解释为"idata”。
(2)如果在“[]”里用寄存器,比如bx,间接给出内存单元的偏移地址,则段地址默认在 ds 中。当然,也可以显式地给出段地址所在的段寄存器。
6 Loop和[BX]的联合应用
计算 ffff:0~ffff:b 单元中的数据的和,结果存储在 dx 中。
assume cs:code
code segmentmov ax,Offffhmov ds,axmov bx,0 ;初始化 ds:bx 指向 ffff:0mov dx,0 ;初始化累加寄存器dx, (dx)=0mov cx,12 ;初始化循环计数寄存器cx, (cx)=12s: mov al, [bx]mov ah,0add dx,ax ;间接向dx中加上((ds) *16+ (bx) )单元的数值inc bx ;ds:bx 指向下一个单元loop smov ax,4c00hint 21hcode ends
end
加一指令inc:
inc a ;相当于 add a,1 //i++
减一指令dec:
dec a ;相当于 sub a,1 //i--
“mov al,[bx]"中的bx就可以看作一个代表内存单元地址的变量,我们可以不写新的指令,仅通过改变bx中的数值,改变指令访问的内存单元。
7 段前缀
指令“mov ax,[bx]”中,内存单元的偏移地址由 bx 给出,而段地址默认在ds中。我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器。比如:
mov ax,ds:[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的"ds:" “es:” “ss:” “es:”,在汇编语言中称为段前缀。
参考文档:
- 《汇编语言 第3版》 王爽 清华大学出版社