一. 显示字符串
1. 需求
显示字符串是现实工作中经常要用到的功能,应该编写一个通用的子程序来实现这个功能。我们应该提供灵活的调用接口,使用者可以决定显示的位置(行、列)、内容和颜色。
子程序描述
名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结尾的字符串
参数:(dh) = 行号(取值范围在0 ~ 24),dl = 列号(取值范围在0 ~ 79),
(cl) = 颜色,ds:si指向字符串的首地址
返回值:无
2. 分析
在8086CPU中,B8000h ~ BFFFFh 这32kb空间,是80 x 25彩色字符模式的显示缓冲区,显示缓冲区分为8页,每页有4kb(约等于4000字节),显示器可以显示任意一页的内容,一般情况下,显示第0页的内容,也就是说,B8000h ~ B8F9F 这4000个字节的内容会显示在显示器上。所以,可以让es寄存器指向显示缓冲器的首地址,作为显示缓冲区的段地址:es = 0B800h
在 B8000h ~ B8F9F 这4000个字节中,分为25行,每一行占160个字节,显示80个字符,每个字符占2字节,低字节存放字符的assii码,高字节存放字符的属性,关于字符属性(闪烁,背景(底色),高亮,前景(字符色))等介绍,可以浏览以下博文了解实验9 根据材料编程《汇编语言》- 王爽-CSDN博客
所以,绿色字的字符属性为:0000 0010b = 2
3. 代码
示例:在屏幕的 8 行 3 列,用绿色显示data段中的字符串
assume cs:code, ds:data
data segmentdb 'welcome to masm!',0
data ends
code segment
start:mov ax, datamov ds, axmov dh, 8mov dl, 3mov cl, 2 ;字属性字节mov si, 0call show_strmov ax, 4c00hint 21h;名称:show_str
;功能:在指定的位置,用指定的颜色,显示一个以0结尾的字符串
;参数:dh=行号(取值范围:0-24),dl=列号(取值范围:0-79)
; cl=颜色,ds:si指向字符串的首地址
;返回值:无
show_str:;B8000h-BFFFFh共32kb,是80 * 25彩色字符模式的显示缓冲区,每页4kb(4000字节),显示器可以显示任意一页的内容。;一般情况下,显示第0页的内容, 也就是说,B8000h ~ B8F9Fh 这4000个字节的内容会显示在显示器上。每行80个字,;每个字占2个字节,低字节存放字符的assii码,高字节为属性字节。所以每行占160个字节mov ax, 0B800h ;显示缓冲区段地址mov es, ax;行mov al, 160 ;每行160个字节mul dh ;8位乘法,一个数默认在al中,另一个在dh中,结果放在ax中mov bx, ax ;行偏移字节;列mov al, 2 ;一个字占2个字节,低字节存放字符的assii码,高字节为属性字节mul dl ;8位,乘法,一个数默认在al中,另一个在dl中,结果放在ax中add bx, ax ;行偏移字节 + 列偏移字节 = 相对于显示缓冲区段地址总的偏移地址mov ch, 0mov dl, cl ;循环中需要用到cx寄存器,所以把存放字体属性的字节存放在dl中
s:mov cl, ds:[si]jcxz ok ;cx = 0,跳转到标号处执行,否则,继续顺序往下执行mov es:[bx], clmov es:[bx+1], dlinc siadd bx, 2loop sok: retcode ends
end start
二. 解决除法溢出问题
1. 需求
div 指令做除法,当进行8位除法的时候,用al存储结果的商,ah存储结果的余数;当进行16位除法的时候,用ax存储结果的商,dx存储结果的余数。可是现在有一个问题,如果结果的商大于al或ax所能存储的最大值,那么将引发CPU的一个内部错误,这个错误被称为:除法溢出,我们可以用下面的子程序 divdw 解决
子程序描述
名称:divdw
功能:进行不会产生溢出的除法运算,被除数为 dword 型,除数为 word 型,结果为 dword 型
参数:(ax) = dword 型数据的低16位
(dx) = dword 型数据的高16位
(cx) = 除数
返回:(ax) = 结果的低16位,(dx) = 结果的高16位
(cx) = 结果的余数
2. 分析
数学公式:X/N = int(H/N)*65535 + [rem(H/N)*65535 + L]/N
X:被除数,范围:[0, FFFFFFFF]
N:除数,范围:[0, FFFF]
H:X的高16位,范围:[0, FFFF]
L:X的低16位,范围:[0, FFFF]
int():描述性运算符,取商,比如,int(38/10) = 3
rem():描述性运算符,取余,比如,int(38/10) = 8
所以,dword 型数据 X 除以 word 型数据 N,可以转换成等号右边的公式来表达,而等号右边的所有除法运算都可以用div指令来做,而且肯定不会导致溢出问题。
3. 代码
示例:计算1000000/10(F4240h/0Ah)
assume cs:code, ds:stack
stack segment dw 16 dup (0)
stack ends
code segment
start:;初始化数据段mov ax, stackmov ss, axmov sp, 16mov ax, 4240h ;ax = 被除数的低16位mov dx, 0Fh ;dx = 被除数的高16位mov cx, 0Ah ;cx = 除数call divdwmov ax, 4c00hint 21h;1000000/10(F4240h/0Ah) = int(H/N)*65535 + [rem(H/N)*65535 + L]/N;名称: divdw;功能: 进行不会产生溢出的除法运算,被除数为 dword 型,除数为 word 型,结果为 dword 型;参数: (ax) = dword 型数据的低16位; (dx) = dword 型数据的高16位; (cx) = 除数;返回: (ax) = 结果的低16位,(dx) = 结果的高16位; (cx) = 结果的余数
divdw:push si ;保存原sipush ax ;被除数的低16位push dx ;被除数的高16位;int(H/N)*65535pop ax ;被除数的高16位mov dx, 0div cx ;16位除法,被除数为32位,ax存放被除数的低16位,dx存放被除数的高16位;结果的商存在ax,结果的余存在dxmov si, ax ;si暂存最后结果的高16位,最后要还原到dx寄存器中的;[rem(H/N)*65535 + L]/N ;上一个除法,已经将rem(H/N)*65535放在dx寄存器中了pop ax ;被除数的低16位, 也就是公式中的 Ldiv cx ;16位除法,被除数为32位,ax存放被除数的低16位,dx存放被除数的高16位;结果的商存在ax,结果的余存在dxmov cx, dx ;余数入cxmov dx, si ;高16位入dxpop si ;还原原siretcode ends
end start
我们知道,1000000/10 的商为 100000 = 186A0h,余数为 0,程序执行的结果为:ax = 86A0h,dx = 1,cx = 0,所以我们的程序代码是正确的。
三. 数值显示
1. 需求
编程,将data段中的数据以十进制的形式显示出来
data segment
dw 123, 12666, 1, 8, 3, 38
data ends
2. 分析
(1)根据书中材料,我们可以知道,字符'0' ~ '9'的assii码为30h ~ 39h,所以对于数字0 ~ 9,他们对应的字符的assii码为:数字的值 + 30h。
(2)对于一个数字,比如:12666,对这个数字循环取余,直到余数为0时,就可以得到每这个数字所有的数码:1,2,6,6,6,对每个数码 + 30h得到每个数码对应的字符的assii码从而得到对应的字符串。然后通过需求一:显示字符串的show_str子程序显示在显示器上。
3.代码
assume cs:code, ds:data, ss:stack
stack segmentdb 32 dup (0)
stack ends
data segmentdb 10 dup (0)
data endscode segment
start:mov ax, stackmov ss, axmov sp, 32mov ax, datamov ds, axmov ax, 12666mov si, 0call dtocmov dh, 8mov dl, 3mov cl, 2mov si, 0call show_strmov ax, 4c00hint 21h;名称:dtoc
;功能:将dword型数据转为表示十进制数的字符串,字符串以 0 为结尾
;参数:(ax) = dword 型数据
; (ds:si)指向字符串的首地址
;返回:无
dtoc: mov bx, 0 ;bx用来记录数字的总数码数,比如12666有5个mov di, 10 ;十进制数
loop_dtoc:mov dx, 0 ;16位除法,被除数的低16位在ax,被除数的高16位为0div di ;16位除法,结果的商在ax中,余数在dx中mov cx, dx ;余数入cxjcxz ok_dtoc ;cx = 0,则跳转到标号处执行,否则继续顺序往下执行add dx, 30h ;数字+30h = 数字对应的字符的assii值push dx ;字符入栈inc bxloop loop_dtoc
ok_dtoc:mov cx, bx
char_nums:pop axmov ds:[si], al ;因为assii码的值不会大于255的,所以用 alinc si ;ds:si指向下一个字符单元loop char_numsmov byte ptr ds:[si], 0 ;字符串以0结尾 ret ;名称:show_str
;功能:在指定的位置,用指定的颜色,显示一个以0结尾的字符串
;参数:dh=行号(取值范围:0-24),dl=列号(取值范围:0-79)
; cl=颜色,ds:si指向字符串的首地址
;返回值:无
show_str:;B8000h-BFFFFh共32kb,是80 * 25彩色字符模式的显示缓冲区,每页4kb(4000字节),显示器可以显示任意一页的内容。;一般情况下,显示第0页的内容, 也就是说,B8000h ~ B8F9Fh 这4000个字节的内容会显示在显示器上。每行80个字,;每个字占2个字节,低字节存放字符的assii码,高字节为属性字节。所以每行占160个字节mov ax, 0B800h ;显示缓冲区段地址mov es, ax;行mov al, 160 ;每行160个字节mul dh ;8位乘法,一个数默认在al中,另一个在dh中,结果放在ax中mov bx, ax ;行偏移字节;列mov al, 2 ;一个字占2个字节,低字节存放字符的assii码,高字节为属性字节mul dl ;8位,乘法,一个数默认在al中,另一个在dl中,结果放在ax中add bx, ax ;行偏移字节 + 列偏移字节 = 相对于显示缓冲区段地址总的偏移地址mov ch, 0mov dl, cl ;循环中需要用到cx寄存器,所以把存放字体属性的字节存放在dl中
loop_show_str:mov cl, ds:[si]jcxz ok_show_str ;cx = 0,跳转到标号处执行,否则,继续顺序往下执行mov es:[bx], clmov es:[bx+1], dlinc siadd bx, 2loop loop_show_strok_show_str: ret
code ends
end start