汇编语言学习(五)
本部分为王爽《汇编语言》第10章的三个实验。
主要内容为:
1.实现对存储的字符串或二进制数据的屏幕显示
2.理解并改进div
存在的溢出问题
1.实验一 显示字符串
子程序描述:
名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:dh
行号,取值0~24,dl
列号,取值0~79,cl
颜色
重要思路:
子程序可以分为定位坐标和复制粘贴数据两部分。
1.根据第9章的实验可知,如果要显示字符串,需要从B8000
开始。所以首先计算dh、dl
对应行、列的实际坐标。尤其注意列是从0开始的。
2.用jcxz 标号
实现读到data段为0,即跳转。
3.为简化程序,多用其它寄存器。比如用al
存储颜色,所以cx
就可以用来从data段中取数了。
assume cs:code
data segment
db 'Welcome to masm',0
data ends
code segment
start: mov dh,14
mov dl,15
mov cl,2
mov ax,data
mov ds,ax
mov si,0
call show_str
mov ax,4C00h
int 21h
show_str: mov ax,0
mov al,dh
mov bl,0Ah
mul bl
add ax,0B800h
mov bx,0
mov bl,dl
sub bl,1
add bl,bl ;列从0开始
mov es,ax
mov al,cl
mov ch,0
s: mov cl,[si]
jcxz ok ;实现读到0跳转
mov es:[bx],cl
mov es:[bx+1],al
add bx,2
add si,1
jmp short s
ok: ret
code ends
end start
此处应该有图片显示实验结果:(为使显示不受干扰,把前面多行的第3列空出来了)
2.实验二 解决除法溢出的问题
问题:当进行8位除法的时候,al
存储结果的商,ah
存储结果的余数。当进行16位除法的时候,ax
存储结果的商,dx
存储结果的余数。如果商比al
或者ax
大,会发生什么呢?
比如:
mov bh,1
mov ax,1000
div bh
进行的8位的除法,但是结果为1000大于255,al
里面放不下,就会发生除法溢出。
这个问题通过下面的子程序解决:
子程序描述:
名称:divdw
功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
参数:(ax)
=dword型数据低16位,(dx)
=dword型数据高16位,(cx)
等于除数。
返回:(ax)
=结果的低16位,(dx)
=结果的高16位,(cx)
等于余数。
对于该计算给出公式:
X:被除数
N:除数
H:X高16位
L:X低16位
int()
:描述性运算符,取商
rem()
:描述性运算符,取余数
公式:X/N = int(H/N)*65536 + int(rem(H/N)*65536+L)/N)
重要思路:
将一次除法转换为两次除法。
0000H 除以 cx一次,得到商A,余数B
BL除以cx一次,得到商C,余数D
AC就是最终的商,D为最终的余数。
assume cs:code
code segment
start: mov ax,4240h
mov dx,000Fh
mov cx,0Ah
call divdw
mov ax,4C00h
int 21h
divdw: mov bx,ax ;bx=L,dx=H,ax=L,cx=N
mov ax,dx
mov dx,0 ;bx=L,dx=0,ax=H,cx=N
div cx ;bx=L,dx=rem(H/N),ax=int(H/N),cx=N
mov si,ax
mov ax,bx ;dx=rem(H/N),ax=L,cx=N,si=int(H/N)
div cx ;dx=rem(X/N),ax=int((rem(H/N)*65536+L)/N),cx=N,si=int(H/N)
mov cx,dx
mov dx,si ;dx=int(H/N),ax=int((rem(H/N)*65536+L)/N),cx=rem(X/N)
ret
code ends
end start
3.实验三 数据显示
将数据以十进制的形式显示出来。
子程序描述:
名称:dtoc
功能:将word型数据转变为表示十进制数的字符串。
参数:(ax)
等于word型数据
子程序主要可以分为三部分:
1.将二进制转化为十进制,每次除法的余数放入栈中。
2.从栈中取出数据,写入data段中。
3.用实验一编写的子程序显示字符串。
需要注意的几点:
1.栈的使用
由于余数是倒着取出来,但是需要正着放入data段,所以需要用到栈。
为在从栈中取出数据时,便于判断放入的数据是否已经取完,可以提前在栈中放一个0作为结束标记。这样用jcxz
判断是否循环结束,也会非常方便。
如下面程序中的push si
就是为实现这个小功能。
2.除法
如果只是用一个div dl
,存在溢出的可能性。所以这里用到了实验二中防溢出的思路。
但下面的程序用上了dx
,将10视作16位数据,防溢出会比实验二的方法要简便。
另外,先放余数,再检验商是否为0比较方便。
还需要注意的是,需要在放入栈中时,就将数字加上30h。因为可能余数会为0,如果不加上30h,会被误判为结束标记。
而且,s0循环必须用jmp
不能用loop
。因为loop
的原理是,每执行一次cx
的值减一,cx
减为0向下执行。这种性质会干扰程序的执行。
3.调试时遇到的问题
开始,我的栈设的16个字节大小,ax的值为12666。每当到把’2’放进去,执行jmp
时,就会强制退出,并弹出如下界面:
我就很迷茫,改了一些地方,调试了很多次也没有改善。后来查到:
我一想,我设了8个字型大小的栈。call
指令用去1个,接着又放进去3个,第4个时就崩了。很有可能就是这个问题。于是把栈开成16个字型大小,问题解决。
这告诉我们:栈还是开大一点好啊~
下面是程序:
assume cs:code,ds:data,ss:stack
data segment
db 10 dup (0)
data ends
stack segment
db 32 dup (0)
stack ends
code segment
start: mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,32
mov ax,17800
call dtoc
mov dh,8
mov dl,3
mov cl,2
call show_str
mov ax,4C00h
int 21h
dtoc: mov bx,10
mov si,0
push si
s0: mov dx,0
div bx
mov cx,dx
add cx,30h
push cx
mov cx,ax
jcxz s1
jmp short s0
s1: pop cx
jcxz ok
mov [si],cl
inc si
jmp short s1
show_str: mov ax,0
mov al,dh
mov bl,0Ah
mul bl
add ax,0B800h
mov bx,0
mov bl,dl
sub bl,1
add bl,bl
mov es,ax
mov al,cl
mov ch,0
mov si,0
s: mov cl,[si]
jcxz ok
mov es:[bx],cl
mov es:[bx+1],al
add bx,2
add si,1
jmp short s
ok: ret
code ends
end start
麻烦一点的程序当然要放一下实验结果啦,挑了两个特殊一点的数:
上一篇: 数据结构与算法(java)——栈和队列