实验十
题目预览
- 显示字符串
- 解决除法溢出的问题
- 数值显示
1
问题:
显示字符串是现实工作中经常要用到的功能,应该编写一个通用的子程序来实现这个功能。我们应该提供灵活的调用接口,使调用者可以决定显示的位置(行、列)、内容和颜色。
子程序描述:
名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:(dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79),(cl)=颜色,ds:si指向字符串的首地址
返回:无
应用举例:在屏幕的8行3列,用蓝色显示data段中的字符串。
assume cs:code
data segment
db 'Welcome to masm!',0
data ends
code segment
start: ··
··
··
code ends
end start
有了实验九的基础我们知道,显示器是25*80。屏幕一行有80个字符串,占160个字节,即一个字符串占两个字节。有25行。要注意的本题的行取值范围是0~24,列取值范围是0~79。
程序如下:
assume cs:code
data segment
db 'Welcome to masm!',0
data ends
code segment
start:
mov dh,8
mov dl,3
mov cl,2
mov ax,data
mov ds,ax
mov si,0
call show_str
mov ax,4c00h
int 21h
show_str:
push si ;有冲突的寄存器放入栈中
push cx
mov bl,cl ;后面要用到cl因此把数据放入bx寄存器的低八位
mov ax,0B800H ;显存的起始地址
mov es,ax
sub di,di
mov ax,160 ;计算显示位置
mul dh
add di,ax
mov ax,2
mul dl
add di,ax ;计算显示位置
green:
mov cl,ds:[si]
mov ch,0
jcxz ok ;判断是否为0
mov es:[di],cx
inc di
mov es:[di],bl
inc di
inc si
jmp short green
ok:
pop cx
pop si
ret
code ends
end start
效果如下:
改为0行0列
2
问题:
前面讲过,div指令可以做除法。当进行8位除法的时候,用al存储结果的商,ah存储结果的余数;进行16位除法的时候,用ax存储结果的商,dx存储结果的余数。可是,现在有一个问题,如果结果的商大于al或ax所能存储的最大值,那么将如何?
我们在用div指令做除法的时候,很可能发生这种情况:结果的商过大,找出了寄存器所能存储的范围。当CPU执行div等除法指令的时候,如果发生这种的情况, 将引发CPU的一个内部错误,这个错误被称为:除法溢出。
好了,我们已经清楚了问题的所在:用div指令做除法的时候可能产生除法溢出。由于有这样的问题,在进行除法运算的时候要注意除数和被除数的值,比如1000000/10就不能用div指令来计算。那么怎么办呢?我们用下面的子程序divdw解决。
子程序描述:
名称:divdw
功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型, 结果为dword型。
参数:
(ax)=dword型数据的低16位
(dx)=dword型数据的高16位
(cx)=除数
返回:
(dx)=结果的高16位,(ax)=结果的低16位
(cx)=余数
应用举例:计算1000000/10(F4240H/0AH)
mov ax,4240H
mov dx,000FH
moc cx,0AH
call divdw
结果:(dx)=0001H,(ax)=86A0H,(cx)=0
提示
给出一个公式:
X:被除数,范围:[0,FFFFFFFF]
N:除数,范围:[0,FFFF]
H:X高16位,范围:[0,FFFF]
L:X低16位,范围:[0,FFFF]
int():描述性运算符,取商,比如,int(38/10)=3
rem():描述性运算符,取余数,比如,rem(38/10)=8
公式:X/N=int(H/N)*65536+[rem(H/N)*65536+L]/N
分析:
看看这个公式的应用,如计算1000000/10(F4240H/0AH)
X=F4240H,N=0AH,H=000FH,L=4240H
int(H/N)=int(0FH/0AH)=int(15/10)=1
rem(H/N)=rem(0FH/0AH)=int(15/10)=5
X/N=1*65536+[5*65536+4240H]/0AH=10000H+[50000H+4240H]/0AH
=10000H+86A0H(换成十进制计算在换成十六进制)=186A0H,结果就是(dx)=0001H(高位),(ax)=86A0H(低位),(cx)=0(余数)。
从材料中总结出
结果高位:Int(H/N)。
结果低位:[rem(H/N)*65536+L]/N的商。
结果余数:[rem(H/N)*65536+L]/N的余数。
[rem(H/N)*65536+L]中rem(H/N)*65536是高位,L是低位。如上面的0001H*65536变成10000H
从上面的公式推出主要过程
mov ax,dx ;H
div cx ;得到ax=int(H/N) bx=rem(H/N)
也不用在去乘65536,放在dx中即高位0001H就是10000H。
完整程序如下:
assume cs:code
code segment
start:
mov ax,4240H ;低16位
mov dx,000FH ;高16位
mov cx,0AH
call divdw
mov ax,4c00h
int 21h
divdw:
mov bx,ax
mov ax,dx
sub dx,dx
div cx ;得ax=int(H/H) dx=rem(H/N)
push ax ;结果高位入栈
mov ax,bx
div cx ;得ax=(结果低位)和dx=(结果余数)
push ax;
push dx;
pop cx
pop ax
pop dx
ret
code ends
end start
第一个div指令
第二个div指令,上一个div指令把余数5放到了dx寄存器中,然后在这个div指令中dx是高位,L是ax,也就相当于了上面公式的[rem(H/N)*65536+L]/N,[5H*65536+4240H]/0AH=[50000H+4240H]/0AH=54240H/0AH=344640/10=34464=86A0H
3
问题:
编程,将data段中的数据以十进制的形式显示出来。
data segment
dw 123,12666,1,8,3,38
data ends
子程序描述:
名称:dtoc
功能:将word型数据转变为表示十进制数的字符串,字符串以0位结尾符。
参数:(ax)=word型数据 ds:si指向字符串的首地址
返回:无
应用举例:编程,将数据12666以十进制的形式在屏幕的8行3列,用绿色显示出来。在显示时我们调用本次实验中的第一个子程序show_str。
assume cs:code
data segment
db 10 dup (0)
data ends
code segment
start:
mov ax,12666
mov bx,data
mov ds,bx
mov si,0
call dtoc
mov dh,8
mov dl,3
mov cl,2
call show_str
··
··
··
code ends
end start
思路,div 10得到余数+30H放入栈中,div 10得到的商循环。
关键代码如下:
mov ax,12666
pushAscii:
mov cx,10
sub dx,dx
div cx ;ax=商,dx=余数
mov cx,ax
jcxz lone ;商为0
add dx,30H
push dx
inc di ;记录有多少条数据
jmp short pushAscii
lone: ;转换最后一个余数为ACSII
add dx,30H
push dx
inc di
mov cx,di ;将有多少条数据传入cx
此时栈中数据如下:
出栈放入data段中,就变成和本实验中的(1)一样了
关键代码如下:
popAscii:
pop ax
mov ds:[si],al ;ACSII占用低8位,配合show_str下的green里的mov cl,ds:[si]
inc si
loop popAscii
data段的内容如下:
下面就和本实验中的(1)一样了
完整代码如下:
assume cs:code
data segment
db 10 dup (0)
data ends
code segment
start:
mov ax,12666
mov bx,data
mov ds,bx
mov si,0
call dtoc
mov dh,8
mov dl,3
mov cl,2
call show_str
mov ax,4c00h
int 21h
dtoc:
push ax
push si
sub di,di
pushAscii:
mov cx,10
sub dx,dx
div cx ;ax=商,dx=余数
mov cx,ax
jcxz lone ;商为0
add dx,30H
push dx
inc di ;记录有多少条数据
jmp short pushAscii
lone: ;转换最后一个余数为ACSII
add dx,30H
push dx
inc di
mov cx,di ;将有多少条数据传入cx
popAscii:
pop ax
mov ds:[si],al ;ACSII占用低8位,配合show_str下的green里的mov cl,ds:[si]
inc si
loop popAscii
pop si
pop ax
ret
show_str:
push si ;有冲突的寄存器放入栈中
push cx
mov bl,cl ;后面要用到cl因此把数据放入bx寄存器的低八位
mov ax,0B800H ;显存的起始地址
mov es,ax
sub di,di
mov ax,160 ;计算显示位置
mul dh
add di,ax
mov ax,2
mul dl
add di,ax ;计算显示位置
green:
mov cl,ds:[si]
mov ch,0
jcxz greenOk ;判断是否为0
mov es:[di],cx
inc di
mov es:[di],bl
inc di
inc si
jmp short green
greenOk:
pop cx
pop si
ret
code ends
end start
运行结果: