1.显示字符串
子程序描述
名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:(dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79),
(cl)=颜色,ds:si指向字符串的首地址
返回:无
应用举例:在屏幕的8行3列,用绿色显示data段中的字符串。
提示
(1)子程序的入口参数是屏幕上的行号和列号,注意在子程序内部要将它们转化为显存中的地址,首先在分析一下屏幕上的行列位置和显存地址的对应关系;(参见:王爽《汇编语言》实验9)
(2)注意保存子程序中用到的相关寄存器
(3)这个子程序的内部处理和显存的结构密切相关,但是向外提供了显存结构无关的接口。通过调用这个子程序,进行字符串的显示时可以不必了解显存的结构,为编程提供了方便。在实验中,注意体会这种设计思想。
参考代码:
; 在屏幕的8行3列,用绿绿显示data段中的字符串
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
; 在指定的位置,用指定的颜色,显示一个用0结束的字符串
; 参数: (dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79),
; (cl)=颜色,ds:si指向字符串的首地址
; 返回:无
show_str: mov ax,0B800H
mov es,ax
mov di,0 ; 显存0页的起始位置,0行,0列
mov al,0A0H
mul dh
add di,ax ; 计算行的开始位置
mov al,2
mul dl
add di,ax ; 计算列的开始位置
mov bl,cl ; 保留颜色值
s: mov cl,[si] ; 取得显示字符
mov ch,0
jcxz ok
mov es:[di],cl ; 将字符写入显存
mov es:[di+1],bl
add di,2
inc si
loop s
ok: ret
code ends
end start
在MS-DOS 7.10下运行结果:
2.解决除法溢出问题
子程序描述
名称:divdw
功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
参数:(ax)=dword型数据的低16位
(dx)=dword型数据的高16位
(cx)=除数
返回:(dx)=结果的高16位,(ax)=结果的低16位
(cx)=余数
应用举例:计算1000000/10(FA240H/0AH)
提示
结出一个公式:
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
这里的65536为10000H,也就是说,int(H/N)*10000H为高16位的商,而[rem(H/N)*65536+L]/N得到的是低16位的商和余数;
这个公式将可能产生溢出的除法运算:X/N,转变为多个不会产生溢出的除法运算公式中,等号右边的所有除法运算都可以用div指令来做,肯定不会导致溢出。
参考代码:
assume cs:code
code segment
start: mov ax,4240H
mov dx,000FH
mov cx,0AH
call divdw
mov ax,4c00h
int 21h
; 功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型
; 参数:(ax)=dword型数据的低16位
; (dx)=dword型数据的高16位
; (cx)=除数
; 返回:(dx)=结果的高16位,(ax)=结果的低16位
; (cx)=余数
divdw: mov bx,ax ; 保存低位
mov ax,dx
mov dx,0H ; 进行H/N 的运算
div cx ; 因为除数为16位的,使用div,则设置的被除数为32位
mov si,ax
; 用于保存int(H/N),因为rem(H/N)*65536就相当于高16位,dx了,故得到的余数在dx直接做高位
mov ax,bx ; [rem(H/N)*65536+L]
div cx ; [rem(H/N)*65536+L]/N,结果是的ax保存为16位的低位
mov cx,dx ; 将余数写入cx
mov dx,si ; 将高位写入dx
ret
code ends
end start
运行结果:
由AX和DX可知:这里得到的结果为186A0H=100000;余数CX为0,结果正确。
3.数值显示
子程序描述
名称:dtoc
功能:将word型数据转变为表示十进制数的字符串,字符串以10为结尾符。
参数:(ax)=word型数据,ds:si指向字符串首地址
返回:无
应用举例:编程,将数据12666以十进制的形式在屏幕的8行3列,用绿色显示出来。在显示时我们调用本次实验中的第一个子程序show_str.
提示
0~9的ASCII码为30H~39H,则先由十六进制转化为十进制,再转ACSII码,转换示意图如下:
同时要注意,依次得到的是个位,十位,百位……万位这与我们习惯的从左到右的万位……个位顺序颠倒。
参考代码:
assume cs:code
data segment
db 10 dup (0)
; 用于存入转换为ASCII码的数据
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
; 功能:将word型数据转变为表示十进制的字符串,字符串以0为结尾符
; 参数: (ax)=word型数据,ds:si指向字符串首地址
; 返回: 无
dtoc: mov si,8 ; 从存ASCII码数据段倒数第2个字节单元开始,相当于添加结尾符0
s0: mov cx,10
mov dx,00
div cx
add dx,30H ; 余数+30H 得到ASCII码
mov [si],dl
mov cx,ax
jcxz break ; 判断商是否为0,如果为0则返回
dec si
inc cx ; 注意在执行loop循环时,先执行的是CX=CX-1,然后判断CX是否为0
loop s0
break: ret
show_str: mov ax,0B800H
mov es,ax
mov di,0
mov al,0A0H
mul dh
add di,ax
mov al,2
mul dl
add di,ax
mov bl,cl
s: mov cl,[si]
mov ch,0
jcxz ok
mov es:[di],cl
mov es:[di+1],bl
add di,2
inc si
loop s
ok: ret
code ends
end start
MS-DOS 7.10下运行结果:
总结:
这里的子程序都只是以实现编程目地而编写的,不具有一定的通用性,因为在每一个子程序中都直接修改寄存器来做为临时变量,8086CPU中的寄存器是有限的,这样可以在使用ret指令退回到主程序时可以造成寄存器冲突,解决的办法是使用如下编程框架:
子程序开始:子程序中使用的寄存器入栈
子程序内容
子程序中使用的寄存器出栈
返回(ret,retf)