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位寄存器中。
(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位数据
②(dl)=(dl)+内存中的8位数据。
第一种方法中的问题是两个运算对象的类型不匹配,第二种方法的问题是结果有可能超界。
怎样解决这两个看似矛盾的问题?目前的方法(在后面的课程中我们还有别的方法)就是得用一个16位寄存器来做中介。将内存单元中的8位数据赋值到一个16位寄存器ax中,再将ax中的数据加到dx上,从而使两个运算对象的类型匹配并且结果不会超界。
想清楚以上的问题之后,编写程序如下。
程序5.5
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:0单元的数值
mov al,ds:[0bh]
mov ah,0 ;(ax)=((ds)*16+0bh)=(ffffbh)
add,dx,ax ;向dx中加上ffff:0单元的数值
mov ax,4c00h ;程序返回
int 21h
code ends
end
上面的程序很简单,不用解释,你一看就懂。不过,在看懂了之后,你是否觉得这个程序编的有些问题?它似乎没有必要写那么长。这是累加ffff:0~ffff:b中的12个数据,如果要累加0000:0~0000:7fff中的32K个数据,按照这个程序的思路,将要写将近10万行程序(写一个简单的操作系统也就这个长度了)。
问题 5.4
应用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]指令中的内存单元的偏移地址是不同的,其他都一样。而这些不同的偏移地址是在,≤X≤bH的范围内递增变化的。
我们可以用数学语言来描述这个累加的运算:
$$sum = \sum_{X=0}^{0bH} (ffffh \times 10h+X)$$
从程序实现上,我们将循环做。
(al)=((ds)*16+X)
(ah)=0
(dx)=(dx)+(ax)
一共循环12次,在循环前(ds)=ffffh,X=0,ds:X指向第一个内存单元。每次循环后,X递增,ds:X指向下一个内存单元。
完整的算法描述如下。
初始化:
(ds)=ffffh
X=0
(ds)=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
最后,我们写出程序。
程序5.6
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 ;ds:bx指向下一个单元
loop s
mov ax,4c00h
int 21
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字节(字单元),存放一个字,偏移地址在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”,在汇编语言中称为段前缀。