汇编学习笔记(4) -- [BX] 和 loop 指令
[BX]
和[0]有些类似,
[0] 代表偏移地址为0
要完整地描述一个内存单元,需要两种信息:
①内存单元的地址;②内存单元的长度(类型)。
用[0]表示一个内存单元时,0表示单元的偏移地址,段地址默认在 ds中,
单元的长度(类型)可以由具体指令中的其他操作对象(比如说寄存器)指出。
[bx]同样也表示一个内存单元,它的偏移地址在bx中,比如下面的指令
mov ax,[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元)
存放一个字,偏移地址在bx中,段地址在ds中。
mov al,[bx]
将一个内存单元的内容送入al,这个内存单元的长度为1字节(字单元)
存放一个字节,偏移地址在bx中,段地址在ds中。
[BX] 详解
mov ax,[bx]
在bx中存放的数据作为偏移地址EA,段地址SA默认在ds中,将SA:EA处的数据送入ax
简单来说
[bx]中的数据就是偏移地址,段地址默认在ds中
将 ds*16 + [bx] 所指向的内存单元中的数据 送入ax
mov [bx],ax
bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中
将ax中的数据送入内存SA:EA处。
简单来说
ax中的数据放入 ds * 16 + [bx] 所指向的内存单元
例题
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200916122554331-797773247.png)
mov ax,2000H
mov ds,ax
mov bx,1000H
mov ax,[bx]
inc bx //代表bx中的内容+1,相当于高等语言中的++
inc bx
mov [bx],ax
inc bx
inc bx
mov [bx],ax
inc bx
mov bx,al
inc bx
程序和内存中的情况如上所示,写出程序执行后,21000H ~ 21007H单元中的内容
分析
(1)先看一下程序的前3条指令:
mov ax,2000H
mov ds,ax
mov bx,1000H
这3条指令执行后,ds=2000H,bx=1000H。
(2)接下来,第4条指令:
mov ax,[bx]
指令执行前: ds=2000H,bx=1000H
将 ds*16 + bx(即21000) 指向的内存单元 中的数据 存入 ax
ax = 00BE
(3)接下来,第5、6条指令:
inc bx inc bx
这两条指令执行前bx=1000H,执行后bx=1002H.
inc 使bx里的数据自增1
(4)接下来,第7条指令:
mov[bx] ,ax
指令执行前: ds=2000H,bx=1002H,ax = 00BE
将ax里的数据送入2000:1002处
指令执行后,2000:1002单元的内容为BE,2000:1003单元的内容为00。
(5)接下来,第8、9条指令:
inc bx inc bx
这两条指令执行前bx=1002H,执行后bx=1004H
(6)接下来,第10条指令:
mov[bx],ax
指令执行前:ds=2000H,bx=1004H,
将ax中的数据送入内存2000:1004处
指令执行后,2000:1004单元的内容为BE,2000:1005单元的内容为00。
(7)接下来,第11条指令:
inc bx
这条指令执行前bx=1004H,执行后bx=1005H。
(8)接下来,第12条指令:
mov[bx],al
指令执行前:ds=2000H,bx=1005H
将al中的数据送入内存2000:1005处
指令执行后,2000:1005单元的内容为BE。
(9)接下来,第13条指令:
inc bx
这条指令执行前bx=1005H,执行后bx=1006H.
(10)接下来,第14条指令:
mov[bx],al
指令执行前:ds=2000H,bx=1006H
则mov [bx],al 将把al中的数据送入内存2000:1006处。指令执行后,2000:1006单元
的内容为BE。
程序执行后,内存中的情况如图所示。
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200916122808944-1254136016.png)
loop
loop 指令的格式是: loop标号
通常情况下,我们用loop指令实现循环,cx中存放循环次数
例子
计算2^3
assume cs:code
code segment
mov ax,2
add ax,ax
add ax,ax
mov ax,4c00h
int 21h
code ends
end
同理,计算2^12需要11个add ax,ax
使用loop
assume cs:code
code segment
mov ax,2
mov cx,11 //这里的cx存储的是循环次数,11就是循环11次
s:add ax,ax
loop s
mov ax,4c00h
int 21h
code ends
end
s是标号,不能太长
该程序中它实际标识了一个地址,这个地址有一个指令:add ax,ax(地址范围是标号后面到loop)
loop s
CPU执行loop指令的时候,要进行两步操作:
①先让cx中的数据 - 1
②判断cx中的值,不为零则转至标号处执行程序(循环跳转),如果为零则向下执行(循环结束)
例题:计算123 * 236
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*123,程序改成s: add ax,236
则只需要123次循环即可完成
在debug里跟踪loop程序
计算fffr:0006单元中的数乘以3,结果存储在dx中
分析一波
(1)运算后的结果是否会超出dx所能存储的范围?
fff:0006单元中的数是一个字节型的数据,范围在0~255之间
则用它和3相乘结果不会大于65535,可以在dx中存放下
(2) ffff:6单元是一个字节单元,ax是一个16位寄存器,数据的长度不一样,如何赋值?
注意,我们说的是“赋值”,就是说,让 ax 中的数据的值(数据的大小)和ff:0006单元中的数据的值(数据的大小)相等
8位数据01H和16位数据0001H的数据长度不一样,但它们的值是相等的。
那么我们如何赋值?设ffff:0006单元中的数据是XXH,若要ax中的值和ffff:0006单元中的相等,
ax中的数据应为00XXH。
所以,若实现 fffr:0006单元向ax赋值,应该令 ah 为0,al 为 ffff:0006 内的数据。
程序:
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:bs中的数据,ah为0
mov dx,0 //累加寄存器清零
mov cx,3 //设置循环次数
s : add dx,ax
loop s //循环计算三次
mov ax,4c00h
int 21h //程序返回
code ends
end
第一条指令mov ax,0ffff
在汇编源程序中,数据!绝对!不能以字母开头,所以要在前面加0。
比如,9138h 在汇编源程序中可以直接写为“9138h”,而A000h在汇编源程序中要写为“0A000h”
如果是在dosbox里的debug中,数据可以 以字母开头
可以使用g命令执行到 指定的位置
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200916123017401-421206851.png)
g 0012 代表从cs:ip一直执行到cs:0012处
使用p命令自动循环
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200916123033737-980387108.png)
当出现 loop 0012时(循环开始) 即可使用p,直接一次执行完循环
也可以使用g跳到loop后面的位置
g 0016
debug 和 汇编编译器masm 对指令的不同处理
mov ax,[0]
debug中表示 将 ds:0处的 数据 送入ax中。
但是在汇编源程序中,指令“mov ax,[0]”被编译器当作指令“mov ax,0”处理。
例子:将内存2000:0 , 2000:2 , 2000:3单元中的数据送入al,bl,cl,dl中
在debug中:
mov ax,2000
mov ds,ax
mov al,[0]
mov bl,[1]
mov cl,[2]
mov dl,[3]
在汇编源程序中:
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
debug中运行:
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200916123143180-128335599.png)
masm编译并运行
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200916123214100-1259728506.png)
很明显
以 mov ax,[0] 为例
debug和masm中的解释各不相同
debug: mov al,[0000]
masm: mov al,00
在汇编源程序中,我们可以使用[bx]来实现同样的效果
mov ax,2000h
mov ds,ax
mov bx,0
mov al,[bx]
这样有点麻烦,所以,可以在 [bx]前面加上 段寄存器
mov ax,2000h
mov as,ax
mov al,ds:[0]
比较一下汇编源程序中以下指令的含义。
“mov al,[0]”,含义:将常量0送入al中(与 mov al,0含义相同);
“mov al,ds:[0]”,含义: 将ds * 16 + [0] 指向的 内存单元中的数据送入al中;
“mov al,[bx]”,含义:ds *16+ bx 指向的内存单元中的数据送入al中;
“mov al,ds:[bx]”,含义:与“mov al,[bx]”相同。
在汇编源程序中
要想使用 [...],就必须在前面加 ds:
如果不加,masm就会认为[...] 为 数据而非偏移地址
如果是[bx] masm同样可以将 bx里的数据 作为 偏移地址
当然,你也可以在前面 加段寄存器名,指定位置
loop 和[bx] 的联合应用
例题计算ffff:0 ~ ffff:b 单元中的数据和 结果存储在dx中
先分析一下:
1 运算后的结果是否会超过dx所能存储的范围?
ffff:0 ~ ffff:b 内存单元中的数据都是字节型数据,范围在0 ~ 266之间
12个这样的数据相加,结果不会大于65536,可以存放
2 我们能否将ffff:0 ~ ffff:b中的数据直接累加到dx中?
不行,因为ffff:0 ~ ffff:b 中的数据是8位
不能直接加到16位寄存器中
3 我们能否将ffff:0 ~ ffff:b中的数据累加到dl中 并设置 dh数据为 0 从而实现累加到dx中?
不行 因为dl是8位寄存器 能容纳的数据的范围在 0~ 255 之间
很容易造成进位丢失
主要是两个运算对象长度不匹配 和 数据大于寄存器造成丢失
目前的方法是使用一个16位寄存器作为中介
将内存单元中的8位数据赋值到一个16位寄存器ax中
再将ax中的数据加到dx上
从而使两个运算对象的类型匹配且结果不会超界
使用loop和[bx]解决
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0 ;初始化ds:bx指向ffff:0
mov bx,0 ;初始化累加寄存器dx
mov cx,12 ;设置循环次数位12
S:
mov al,[bx]
mov ah,0
add dx,ax ;间接向dx中加上 ds*16 + bx所指向的 内存单元 里的值
inc bx ;让ds:bx指向下一个单元
loop S
mov ax,4c00h
int 21h
code ends
end
使用循环时
如果遇到必须在每次循环的时候按照同一种方法来改变要访问的内存单元的地址时
就不能用常量来给出内存单元的地址(比如,[0]、[1]、[2]中,0、1、2是常量)
而是应用变量。
“mov al,[bx]”中的bx就可以看作一个代表内存单元地址的变量
我们可以不写新的指令,仅通过改变 bx 中的数值,改变指令访问的内存单元。
段前缀
指令 mov ax,[bx]中
内存单元的偏移地址由bx给出
而段地址默认在ds中
可以在这种指令中显示地给出内存单元的段地址所在的段寄存器
比如:
mov ax,ds:[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元)
存放一个字,偏移地址在bx中,段地址在ds 中。
mov ax,cs:[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为⒉字节(字单元)
存放一个字,偏移地址在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:”,在汇编语言中称为段前缀。
一段安全的空间
在8086模式中,随意向一段内存空间写入内容是很危险的
因为这段空间中可能存放着重要的系统数据或代码。
比如下面的指令:
mov ax,1000h
mov ds,ax
mov al,0
mov ds:[0],al
这种做法是不合理的
因为我们并没有论证过1000:0中是否存放着重要的系统数据或代码
如果1000:0中存放着重要的系统数据或代码,“mov ds:[0],al”将其改写,将引发错误。
dos方式下
一般情况,0:200 ~ 0:2ff这段空间没有数据
段前缀的使用
将内存ffff:0 ~ ffff:b单元中的数据复制到0:200~0:20b单元中
(1)0:200~0:20b单元等同于0020:0~0020:b单元,它们描述的是同一段内存空间。
(2)复制的过程应用循环实现,简要描述如下。
初始化:
x=0
循环12次:
将ffff:x单元中的数据送入 0020:x(需要用一个寄存器中转)
x = x + 1
(3)在循环中,源始单元 ffff:x 和 目标单元0020:x 的 偏移地址 x 是变量
我们用bx来存放。
(4)将0:200~0:20b 用0020:0~0020:b 描述
就是为了使目标单元的偏移地址和源始单元的偏移地址从同一数值0开始。
程序
assume cs:code
code segment
mov bx,0 ;(bx)=0,偏移地址从О开始
mov cx,12 ;循环12次
s:
mov ax,0ffffh
mov ds,ax ;(ds) = Offffh
mov dl,[bx] ;将(ds)*16+(bx) 指向的内存单元中的数据 送入dl
mov ax,0020h
mov ds,ax ;(ds)=0020h
mov [bx],dl ; ( (ds)*16+(bx))= (dl),将中dl的数据送入0020:bx
inc bx
; (bx)=(bx)+1
loop s
mov ax, 4c00h
int 21h
code ends
end
因源始单元ffff:x 和 目标单元 0020:X 相距大于64KB
在不同的64KB段里,程序中,每次循环要设置两次 ds
这样做是正确的,但是效率不高。
我们可以使用两个段寄存器分别存放 源始单元fffd: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 ;(bx)=0,此时ds:bx指向ffff:0 es:bx指向0020:0
mov cx,12 ;(cx)=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
code ends
end
程序中,使用es存放目标空间0020:0~0020:b的段地址
用ds存放源始空间ffff:0~ffff:b的段地址
在访问内存单元的指令“mov es:[bx],al”中,显式地用段前缀“es:”给出单元的段地址
这样就不必在循环中重复设置ds。
参考: 王爽 - 汇编语言 和 小甲鱼零基础汇编