汇编语言-05[BX]和loop指令
[bx]和内存单元的描述
内存单元
mov ax,[0]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为0, 段地址在ds中。
mov al,[0]
将一个内存单元的内容送入al,这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址为0,段地址在ds中。
要完整地描述一个内存单元, 需要两种信息:1.内存单元的地址;2.内存单元的长度(类型)。
用[0]表示一个内存单元时,0表示单元的偏移地址,段地址默认在ds中,单元的长度(类型)可以由具体指令中的其他操作对象(比如说寄存器)指出。
[bx]
[bx]同样也表示一个内存单元,它的偏移地址在bx 中,比如下面的指令:
mov ax,[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。
mov al,[bx]
将一个内存单元的内容送入al,这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址在bx中,段地址在ds中。
loop
循环指令
符号"()”
使用一个描述性的符号”()”来表示一个寄存器或一个内存单元中的内容。
(ax)表示ax中的内容、(al)表示al中的内容;
(20000H)表示内存20000H 单元的内容(()中的内存单元的地址为物理地址);
((ds)*16+(bx))表示:ds中的内容为ADR1,bx中的内容为ADR2,内存ADR1 x16 +ADR2单元的内容。或者ds中的ADR1作为段地址,bx中的ADR2作为偏移地址,内存ADR1:ADR2单元的内容。
”()”中的元素可以有3 种类型: 1.寄存器名; 2.段寄存器名; 3.内存单元的物理地址(一个20 位数据) 。
(ax)、(ds)、(al)、(ex)、(20000H)、((ds)*16+(bx))等是正确的用法;(2000:0)、((ds):1000H)等是不正确的用法,这里没有计算出物理地址。
约定符号idata表示常量
mov ax,[0] , 表示将ds:0处的数据送入ax中。指令中,在“[... ]"里用一个常量0表示内存单元的偏移地址。我们用idata表示常量
[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) 。
inc bx的含义是bx 中的内容加1; mov bx,1 inc bx 执行后,bx=2 。
最终结果
Loop指令
loop 指令的格式是: loop 标号,CPU执行loop指令的时候,要进行两步操作,1.(cx)=(cx) - 1; 2.判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。
用loop 指令来实现循环功能, ex 中存放循环次数。
assume cs:code #代码段code code segment #代码段开始 mov ax,2 #ax寄存器赋值为2 mov cx,11 #cx记录循环次数 s: add ax,ax #循环标记 loop s #判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行 mov ax,4c00h #退出程序 int 21h code ends #代码段结束 end #结束(编译器识别)
标号
标号s,它实际上标识了一个地址,这个地址处有一条指令:add ax,ax。
loop s
CPU 执行loop s的时候,要进行两步操作:
1.(cx)=(cx) - 1;
2.判断cx中的值,不为0则转至标号s所标识的地址处执行(这里的指令是add ax,ax),如果为零则执行下一条指令(下一条指令是mov ax,4c00h) 。
执行loop s时,首先要将(cx)减1,然后若(cx)不为0,则向前转至s处执行add ax,ax。所以,可以利用cx来控制add ax,ax的执行次数。
在cx中存放循环次数;
loop指令中的标号所标识地址要在前面;
要循环执行的程序段,要写在标号和loop指令的中间。
Debug跟踪loop指令实现循环程序
计算ffff:0006单元中的数乘以3,结果存储在dx中。
第一条指令mov ax,0ffffh 。我们知道,大于9FFFh 的十六进制数据A000H 、A001H... C000H、C001H... FFFEH、FFFFH等,在书写的时候都是以字母开头的。而在汇编源程序中,数据不能以字母开头,所以要在前面加0。比如,9138h 在汇编源程序中可以直接写为"9138h",而A000h在汇编源程序中要写为“0A000h"。
loop 指令分两个步骤:第一步先将(cx)减1,即(cx)=2;第二步因为(cx)不等于0 , 将IP设为0012h。指令"loop 0012"执行后。
debug调试时想跳过前边所有步骤可以用"g 偏移地址"直接执行到cs:偏移地址的位置,相当于给指令寄存器IP赋值。
Debug和汇编编译器masm对指令的不同处理
"mov al,[0]"、“mov bl,[1"、“mov cl,[2]"、“mov dl,[3]",但Debug 和编译器对这些指令中的” [idata]" 却有不同的解释。Debug 将它解释为” [idata]" 是一个内存单元,"idata" 是内存单元的偏移地址;而编译器将“[idata]" 解释为“idata" 。
可将偏移地址送入bx 寄存器中, 用[bx] 的方式来访问内存单元。比如我们可以这样访问2000:0 单元:
在“[]”,中直接给出内存单元的偏移地址。这样做,在汇编源程序中也是可以的,只不过,要在“[]"的前面显式地给出段地址所在的段寄存器。
汇编源程序中以下指令的含义:
Debug P2.exe
总结:
1.在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须用”[...],来表示内存单元,如果在“0”里用一个常量idata直接给出内存单元的偏移地址,就要在"[]"的前面显式地给出段地址所在的段寄存器,例如mov al ,ds:[0]。如果没有在“[]"的前面显式地给出段地址所在的段寄存器,例如mov al,[0],那么,编译器masm将把指令中的”[idata]"解释为"idata"。
2.如果在"[]"里用寄存器,比如bx,间接给出内存单元的偏移地址,则段地址默认在ds中。当然,也可以显式地给出段地址所在的段寄存器。
loop和[bx]的联合应用
计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中。
assume cs:codesg codesg segment mov ax,0fffh mov ds,ax mov bx,0 #初始化ds:bx指向ffff:0 mov dx,0 #初始化累加寄存器dx,(dx)=0 mov cx,12 #初始化循环计数寄存器cx,(cx)=12 s:mov al,[bx] mov ah,0 add dx,ax #间接向dx中加上((ds)*16+(bx))单元的数值 inc bx #bx自加一,ds:bx指向下一个单元 loop s mov ax,4c00H int 21H codesg ends end
“mov al,[bx]"中的bx就可以看作一个代表内存单元地址的变量,我们可以不写新的指令,仅通过改变bx中的数值,改变指令访问的内存单元。
段前缀
指令“mov ax, [bx]"中,内存单元的偏移地址由bx给出,而段地址默认在ds中。我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器。比如:
(1) mov ax,ds:[bx]
将一个内存单元的内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。
(2) mov ax,cs:[bx]
将一个内存单元的内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在cs中。
(3) mov ax,ss:[bx]
将一个内存单元的内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ss中。
(4) mov ax,es:[bx]
将一个内存单元的内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在es中。
(5) mov ax,ss:[0]
将一个内存单元的内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为0,段地址在ss中。
(6) mov ax,cs:[0]
将一个内存单元的内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为0,段地址在cs中。
这些出现在访问内存单元的指令中,用千显式地指明内存单元的段地址的"ds:" "cs:" "ss:" "es:", 在汇编语言中称为段前缀。
一段安全的空间
随意向一段内存空间写入内容是很危险的,因为这段空间中可能存放着重要的系统数据或代码。
总结:
(1) 我们需要直接向一段内存中写入内容;
(2) 这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作很可能引发错误;
(3) DOS方式下, 一般情况,0:200~0:2ff空间中没有系统或其他程序的数据或代码; .
(4) 以后,我们需要直接向一段内存中写入内容时,就使用0:200~0:2ff这段空间。
段前缀的使用
将内存ffff:0 - ffff:b单元中的数据复制到0:200 ~ 0:20b单元中。
assume cs:codesg codesg segment mov ax,0ffffh mov ds,ax #(ds)=0ffffh mov ax,0020h mov es,ax #(es) =0020h mov bx,O #(bx)=O, 此时ds:bx指向ffff:0,es:bx指向0020:0 mov cx,12 #(ex)=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 #(bx)=(bx)+1 loop s mov ax,4c00H int 21H codesg ends end
使用es存放目标空间0020:0-0020:b的段地址,用ds存放源始空间而ffff:b的段地址。在访问内存单元的指令“mov es:[bx],al" 中,显式地用段前缀“es:“给出单元的段地址,这样就不必在循环中重复设置ds 。