我的汇编学习([Bx]和loop指令)

1. [bx]和内存单元的描述

[bx]是什么呢?和[0]有些类似,[0]表示内存单元,它的偏移地址是0。比如在下面的指令中:
mov ax, [0]
将一个内存单元的内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为0,段地址在ds中。
mov al, [0]
将一个内存单元的内容送入al,这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址为0,段地址在ds中。

要完整地描述一个内存单元,需要两种信息:①内存单元的地址;②内存单元的长度(类型)。
用[0]表示一个内存单元时,0表示单元的偏移地址,段地址默认在ds中,单元的长度(类型)可以由具体指令中的其他操作对象(比如说寄存器)指出

[bx]同样也表示一个内存单元,它的偏移地址在bx中,比如下面的指令:
mov ax, [bx]
将一个内存单元的内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。
mov al, [bx]
将一个内存单元的内容送入al,这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址在bx中,段地址在ds中。

2. loop

英文单词“loop" 有循环的含义,显然这个指令和循环有关。我们在这一章,讲解[bx]和loop指令的应用、意义和相关的内容。

3.我们定义的描述性的符号:“()”

为了描述上的简洁,在以后的课程中,我们将使用一个描述性的符号**“( )”来表示一个寄存器或一个内存单元中的内容**。比如:
(ax)表示ax中的内容、(al)表示 al中的内容;
(20000H)表示内存20000H单元的内容(( )中的内存单元的地址为物理地址);
((ds)*16+(bx))表示:
ds中的内容为ADR1, bx中的内容为ADR2,内存ADR1X 16+ADR2单元的内容。
也可以理解为:ds中的ADR1作为段地址,bx 中的ADR2作为偏移地址,内存ADR1:ADR2单元的内容。
**注意,“( )”中的元素可以有3种类型:①寄存器名;②段寄存器名;③内存单元的物理地址(一个 20位数据)。*比如:
(ax)、(ds)、 (al)、 (cx)、(20000H)、 ((ds)
16+(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)、 (cI)等得到的数据为字节型;(ds)、 (ax)、 (bx)等得到的数据为字型。
(al)=(20000H),则(20000H)得到的数据为字节型; (ax)=(20000H), 则(20000H)得到的数据为字型。

4.约定符号 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等。

5.1 [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)。

例题:
程序和内存中的情况如图5.1 所示,写出程序执行后,21000H~21007H 单元中的内容。
在这里插入图片描述
注意,inc bx的含义是bx中的内容加1,比如下面两条指令:

mov bx, 1
inc bx .
执行后,bx=2

分析:
(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, 则mov ax,[bx]将把内存2000:1000处的字型数据送入ax中。该指令执行后,ax=00beH。(传输了一整个字单元)
(3) 接下来,第5、6条指令:
inc bx
inc bx
这两条指令执行前bx=1000H,执行后bx= 1002H。
(4) 接下来,第7条指令:
mov [bx] ,ax
指令执行前: ds=2000H, bx=1002H, 则mov [bx],ax 将把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, 则mov [bx],ax 将把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,则mov [bx],al 将把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。
程序执行后,内存中的情况如图5.2所示。在这里插入图片描述

5.2 Loop 指令

loop指令的格式是:loop 标号CPU执行loop 指令的时候,要进行两步操作,①(cx)=(cx)-1;②判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。

从上面的描述中,可以看到,cx 中的值影响着loop 指令的执行结果。通常(注意,我们说的是通常)我们用loop指令来实现循环功能,cx 中存放 循环次数。

这里讲解loop指令的功能,关于loop指令如何实现转至标号处的细节,将在后面的课程中讲解。下面我们通过一个程序来看一下loop指令的具体应用。

任务1:编程计算2^2,结果存在ax中。
分析:设(ax)=2, 可计算(ax)=(ax)2, 最后(ax)中为2^2的值。N2可用N+N实现,程序如下。

assume Cs: code
code segment
	mov ax, 2
	add ax, ax
	mov ax, 4c00h 
	int 21h
code ends
end

任务2:编程计算2^3。
分析: 2^3=2*2*2, 若设(ax)=2,可计算(ax)=(ax)22, 最后(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。
分析: 2^12= 2*2*2*2*2*2*2*2*2*2*2*2,若设(ax)=2,可计算(ax)=(ax)22222222222,最后(ax)中为2^12的值。N2可用N+N实现,程序如下。

assume Cs: code
code segment
	mov ax,2
	;做11次add ax, ax
	mov ax, 4c00h
	int 21h
code ends
end

可见,按照我们的算法,计算2^12需要11条重复的指令add ax,ax。我们显然不希望这样来写程序,这里,可用loop来简化我们的程序。
程序5.1

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

下面分析一下程序5.1。
(1) 标号
在汇编语言中,标号代表一个地址,程序5.1 中有一个标号s。它实际上标识了一个地址,这个地址处有一条指令:add ax,ax。
(2) loops
CPU执行loops 的时候,要进行两步操作:
①(cx)=(cx)-1(这是自动的);
②判断cx中的值,不为0则转至标号s所标识的地址处执行(这里的指令是add ax,ax),如果为零则执行下条指令(下一条指令是mov ax,4c00h)。所以,可以利用cx来控制add ax,ax的执行次数。

从上面的过程中,我们可以总结出用cx和loop指令相配合实现循环功能的3个要点:
(1) 在cx中存放循环次数;
(2) loop指令中的标号所标识地址要在前面;
(3) 要循环执行的程序段,要写在标号和loop指令的中间。
用cx和loop指令相配合实现循环功能的程序框架如下。
mov cx, 循环次数
s:循环执行的程序段
loop s

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位寄存器,能容纳的数据的范围在0~255之间,ffff:0~ffff:b中的数据也都是8位,如果仅向dl中累加12个8位数据,很有可能造成进位丢失。
(4)我们到底怎样将ffff:0~ffff:b中的8位数据,累加到16位寄存器dx中?
从上面的分析中,可以看到,这里面有两个问题:类型的匹配和结果的不超界。具体的说,就是在做加法的时候,我们有两种方法:
①(dx)=(dx)+内存中的 8位数据;
②(dI)=(dI)+内存中的 8位数据。
第一种方法中的问题是两个运算对象的类型不匹配,第二种方法中的问题是结果有可能超界。
怎样解决这两个看似矛盾的问题?目前的方法(在后面的课程中我们还有别的方法)就是得**用一个16 位寄存器来做中介。将内存单元中的8位数据赋值到一个16位寄存器ax中,再将ax中的数据加到dx上,从而使两个运算对象的类型匹配并且结果不会超界。**想清楚以上的问题之后,编写程序如下:

assume Cs: code
code segment
	mov ax, 0ffffh
	mov ds, ax							;设置(ds)=ffffh
	mov dx, 0							;初始化累加寄存器,(dx)=0
	mov al,ds:[0]
	mov ah,0								@;(ax)=((ds)*16+0)= (ffff0h)@
	add dx,ax							;向dx中加上ffff:0单元的数值
	mov al,ds: [1]
	mov ah, 0							;(ax)=((ds) *16+1)=(ffff1h)
	add dx,ax							;向dx中加上ffff:1单元的数值
	mov al,ds: [2]
	mov ah, 0							;(ax)=((ds) *16+2)= (ffff2h)
	add dx,ax							;向dx中加上ffff:2单元的数值
	mov al,ds: [3]
	mov ah,0							;(ax)=( (ds) *16+3)= (ffff3h)
	add dx,ax							;向dx中加上ffff:3单元的数值
	mov al,ds: [4]
	mov ah, 0							;(ax)=((ds) *16+4)= (ffff4h)
	add dx,ax							;向dx中加上ffff:4单元的数值
	mov al,ds: [5]
	mov ah, 0							;(ax)=((ds) *16+5)=(ffff5h)
	add dx, ax							;向dx中加上ffff:5单元的数值
	mov al,ds: [6]
	mov ah, 0							;(ax)=((ds)*16+6)=(ffff6h)
	add dx,ax 							;向dx中加上ffff:6单元的数值
	mov al,ds: [7]
	mov ah, 0							;(ax)=((ds) *16+7)=(ffff7h)
	add dx, ax							;向dx中加上ffff:7单元的数值
	mov al,ds: [8]
	mov ah, 0							;(ax)=((ds) *16+8)= (ffff8h)
	add dx,ax							;向dx中加上ffff:8单元的数值
	mov al,ds: [9]
	mov ah, 0							;(ax)=((ds) *16+9)=(ffff9h)
	add dx,ax							;向dx中加上ffff:9单元的数值
	mov al,ds: [0ah]
	mov ah, 0							;(ax)=((ds) *16+0ah)= (ffffah)
	add dx,ax 							;向dx中加上ffff:a单元的数值
	mov al,ds: [0bh]
	mov ah, 0							;(ax)=( (ds) *16+0bh)= (ffffbh)
	adddx,ax								;向dx中加上ffff:b单元的数值
	mov ax, 4c00h						;程序返回
	int 21h
code ends
end

你是否觉得这个程序编得有些问题?它似乎没有必要写这么长。这是累加ffff:0~ffff:b中的12 个数据,如果要累加0000:0~0000:7fff中的32KB个数据,按照这个程序的思路,将要写将近10万行程序(写一个简单的操作系统也就这个长度了)。

接下来重点来了!!!我们要应用loop指令,改进程序5.5,使它的指令行数让人能够接受。
分析:
可以看出,在程序中,有12个相似的程序段,我们将它们一般化地描述为:

mov al,ds:[X]						;ds:x指向ffff:X单元
mov ah, 0							;@这两部使得(ax)=( (ds) *16+ (X) )=(ffffXh) @
add dx, ax							;向dx中加上ffff:X单元的数值

我们可以看到,12 个相似的程序段中,只有mov al,ds:[X]指令中的内存单元的偏移地址是不同的,其他都一样。而这些不同的偏移地址是在0≤X≤bH的范围内递增变化的。
从程序实现上,我们将循环做:
(al)=( (ds) *16+X)
(ah)=0
(dx)=(dx)+ (ax)
一共循环12次,在循环开始前(ds)=ffffh, X=0,ds:X 指向第一个内存单元。每次循环后,X递增,ds:X 指向下一个内存单元。

完整的算法描述如下:
先初始化: .
(ds)=ffffh
X=0
(dx)=0
循环12次:
(al)=((ds) *16+X)
(ah)=0
(dx)=(dx)+(ax)
X=X+1
**可见,表示内存单元偏移地址的X应该是一个变量,因为在循环的过程中,偏移地址必须能够递增。这样,在指令中,我们就不能用常量来表示偏移地址。我们可以将偏移地址放到bx中,用[bx]的方式访问内存单元。**在循环开始前设(bx)=0,每次循环,将bx中的内容加1即可。
最后一个问题是,如何实现循环12次?我们的loop指令该发挥作用了。
初始化:
(ds)=ffffh
(bx)=0
(dx)=0
(cx)=12
循环12次:
s: (al)=((ds) *16+ (bx) )
(ah)=0
(dx)=(dx)+ (ax)
(bx)= (bx) +1
loop s
最后,我们写出程序:

assume cs: code
code segment
	mov ax,0ffffh
	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
code ends
end

在实际编程中,经常会遇到,用同一种方法处理地址连续的内存单元中的数据的问题。我们需要用循环来解决这类问题,同时我们必须能够在每次循环的时候按照同一种方法来改变要访问的内存单元的地址。这时,就不能用常量来给出内存单元的地址(比如,[0]、 [1]、[2]中,0、1、2是常量),而应用变量。
“mov al,[bx]"中的bx就可以看作一个代表内存单元地址的变量,我们可以不写新的指令,仅通过改变bx中的数值,改变指令访问的内存单元。

5.6 段前缀

指令“mov ax,[bx]” 中,内存单元的偏移地址由bx给出,而段地址默认在ds中。**我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的 段寄存器。**比如:
(1) mov ax,ds:[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元,因为是ax),存放一个偏移地址在bx中,段地址在ds中。
(2) mov ax,cs:[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个偏移地址在bx中,段地址在cs中
(3) mov ax,ss:[0]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个偏移地址为0,段地址在ss中
(4) mov ax,es:[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个偏移地址在bx中,段地址在es中
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:”“cs:” “ss:” “es:” ,在汇编语言中称为段前缀。

5.7 一段安全的空间

在8086 模式中,随意向一段内存空间写入内容是很危险的,因为这段空间中可能存放着重要的系统数据或代码。否则会将其改写,发生错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值