《汇编语言》王爽(第四版) 第十章 实验10
本文是王爽老师《汇编语言》(第四版) 第十章 实验10 的分析及代码。本实验的内容是:编写三个子程序(显示字符串、解决除法溢出的问题、数值显示)。本文是王爽老师《汇编语言》(第四版) 第十章 实验10 编写3个子程序 的分析及代码。这个实验任务要按顺序完成,因为后边的任务会用到前边写的子程序。前言一、子程序1 显示字符串1.实验任务2.分析(1)如何在指定位置显示(2)如何显示指定颜色(3)保存子
文章目录
前言
本文是王爽老师《汇编语言》(第四版) 第十章 实验10 的分析及代码。
本实验的内容是:编写三个子程序(显示字符串、解决除法溢出的问题、数值显示)。
一、子程序1 显示字符串
1.实验任务
编写一个子程序,向外提供可以调用的接口,能够在指定的行号、列号,用指定的颜色,显示一个用0结束的字符串。
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 ;设置ds
mov ds,ax
mov si,0 ;设置ds:[si]指向当前要显示的字符
call show_str ;调用子程序,显示字符串
mov ax,4c00H ;程序返回
int 21H
show_str:
code ends
end start
2.分析
(1)如何在指定位置显示
第一屏的内容所对应的内存地址空间为B8000H~B8F9FH,因而可以令这一段的段地址为B800H。
显示器一屏可以显示25行,每行80个字符(每个字符占2个字节)。因此第一个字符应该在的偏移地址应该是 ( dh*80*2 + dl*2 )。注意,任务中给出的列号是指显示屏上的第几个字符,而不是指该行的第几个字节,显示区每个字符占两个字节!因此,指定的行号参数取值范围是00-24,列号取值范围是00-79 (十进制)。
(2)如何显示指定颜色
属性字节中,最低三位可以用来设置颜色。这个属性字节的内容应当是寄存器cl的值。
(3)保存子程序中用到的寄存器
子程序中会使用到dh dl si 等寄存器,因而在子程序的一开始要把这些寄存器的值压入栈,在子程序返回前再pop出来。由此可知需要一段栈空间。
3.代码
assume cs:code,ds:data,ss:stack
data segment
db 'Welcome to masm!',0 ;要显示的字符串
data ends
stack segment
dw 8 dup(0) ;用于在调用子程序时存放寄存器值
stack ends
code segment
start:
mov dh,8 ;指定行号(从0开始计数)
mov dl,3 ;指定列号(从0开始计数)
mov cl,2 ;指定颜色
mov ax,data ;设置ds
mov ds,ax
mov si,0 ;设置ds:[si]指向data段中当前要显示的字符
mov ax,stack ;设置栈顶
mov ss,ax
mov sp,10H
call show_str ;调用子程序,显示字符串
mov ax,4c00H ;程序返回
int 21H
show_str:
;功能:将data段中首地址为ds:si的字符,以指定颜色显示在屏幕指定位置
;参数:dh 行号(从0开始计数), dl 列号(从0开始计数) ,cl 颜色
;返回:无
push dx ;将子程序用到的寄存器压入栈
push si
push ax
push bx
push es
mov ax,0B800H ;设置es为显示区段地址
mov es,ax
mov ax,00A0H ;设置首字符显示的地址
mul dh
mov dh,0
add ax,dx
add ax,dx
mov bx,ax ;bx是显示区的偏移地址
mov al,cl ;用al存储属性字节
mov ch,0
mov si,0
s: ;循环读取字符并显示
mov cl,ds:[si]
jcxz ok ;若读到0,就退出循环
mov es:[bx],cl
inc bx
mov es:[bx],al
inc bx
inc si
jmp short s
ok: ;将寄存器的值pop出来
pop es
pop bx
pop ax
pop si
pop dx
ret ;返回
code ends
end start
二、子程序2 解决除法溢出的问题
1.实验任务
(1)写一个子程序,功能是进行不会产生溢出的除法运算。被除数为dword型,除数为word型,结果为dword型。
参数:ax = dword型数据的低16位 ;dx = dword型数据的高16位 ;cx = 余数
返回: dx = 结果的高16位 ;ax = 结果的低16位 ; cx = 余数
(2)可以参考的公式如下。
X 为被除数,H 为X的高16位,L 为X的低16位 , N 除数。
int()表示取商,rem()表示取余数。
则 X/N = int( H/N ) * 65536 + [rem( H/N) * 65536 + L]/N 。
(通过实际写代码会对这个公式的巧妙性有更为深入的理解。)
2.代码
assume cs:code,ds:data,ss:stack
data segment ;定义数据段
db 0
data ends
stack segment ;定义栈段
db 0
stack ends
code segment ;定义代码段
start:
mov ax,stack ;设置栈顶
mov ss,ax
mov sp,10H
mov ax,4240H ;被除数的低16位
mov dx,000FH ;被除数的高16位
mov cx,0AH ;除数
call divdw ;调用子程序
mov ax,4c00H ;程序返回
int 21H
divdw: ;功能:改进后的不会溢出的div指令
;参数: ax=被除数低16位,dx=被除数高16位,cx = 除数
;返回: ax=商的低16位,dx=商的高16位,cx = 余数
;计算公式: X/N = int( H/N ) * 65536 + [rem( H/N) * 65536 + L]/N
;其中X为被除数,N为除数,H为被除数的高16位,L为被除数的低16位,
;int()表示结果的商,rem()表示结果的余数。
push bx ;bx是额外用到的寄存器,要压入栈
mov bx,ax ;bx=L
mov ax,dx ;ax=H
mov dx,0 ;dx=0
div cx ;计算H/N,结果的商即int(H/N)保存在ax,余数即rem(H/N)保存在dx
;接下来要计算int(H/N)*65536,思考一下,65536就是0001 0000 H,
;因此计算结果就是,高16位=int(H/N),低16位为0000H。
push ax ;将int(H/N)*65536结果的高16位,即int(H/N),压入栈
mov ax,0
push ax ;将int(H/N)*65536结果的低16位,即0000H,压入栈
;接下来要计算 rem(H/N)*65536 ,同理可得,
;计算结果为 高16位= rem(H/N)*65536 ,即此时dx的值,
;低16位为 0000H。
mov ax,bx ;ax = bx = L ,即 [rem(H/N)*65536 + L]的低16位
;此时dx=rem(H/N),可以看做 [rem(H/N)*65536 + L] 的高16位
div cx ;计算 [rem( H/N) * 65536 + L]/N ,结果的商保存在ax,余数保存在dx
;接下来要将两项求和。 左边项的高、低16位都在栈中,
;其中高16位就是最终结果的高16位,低16位是0000H。
;右边项的商为16位,在ax中,也就是最终结果的低16位,
;余数在dx中,也就是最终结果的余数。
mov cx,dx ;cx = 最终结果的余数
pop bx ;cx = int(H/N)*65536结果的低16位,即0000H。
pop dx ;bx = int(H/N)*65536结果的高16位,即最终结果的高16位
pop bx ;还原bx的值
ret
code ends
end start
三、子程序3 数值显示
1.实验任务
(1)将data段中的数据以十进制的形式显示出来。应该编写一个通用的子程序,将一个给定的word型数据转为以十进制显示的字符串。而字符的显示功能,可以调用上边写的子程序1来完成。
子程序描述:
名称:dtoc
功能:将word型数据转变为表示十进制数的字符串,字符串以0为结尾符。
参数:ax = word型数据 ; ds:si 指向字符串的首地址
返回:无
(2)代码
assume cs:code,ds:data
data segment
db 10 dup(0)
data ends
code segment
start:
mov bx,data ;设置ds段地址
mov ds,bx
mov ax,12666 ;要显示的数据
mov si,0 ;ds:si指向字符串首地址
call dtoc ;将数据转为十进制字符
mov dh,8 ;在屏幕第几行开始显示
mov dl,3 ;在屏幕第几列开始显示
mov cl,2 ;显示的字符的颜色
call show_str
mov ax,4c00H ;程序返回
int 21H
dtoc:
show_str:
code ends
end start
2.显示一个word型数据的代码
这里要把十六进制(或者说二进制)的数据转换为十进制,就要用到除法,也就是div指令,有可能出现除法溢出的问题,可以调用子程序2(解决除法溢出问题)来解决!而转换成十进制的字符数字之后,可以调用子程序1(显示字符串)来显示。
assume cs:code,ds:data,ss:stack
data segment
db 10 dup(0)
data ends
stack segment
dw 16 dup(0) ;32字节
stack ends
code segment
start:
mov bx,data ;设置ds段地址
mov ds,bx
mov bx,stack ;设置栈顶
mov ss,bx
mov sp,20H
mov ax,12666 ;要显示的数据
mov si,0 ;ds:si指向字符串首地址
call dtoc ;将数据转为十进制字符
mov dh,8 ;在屏幕第几行开始显示
mov dl,3 ;在屏幕第几列开始显示
mov cl,2 ;显示的字符的颜色
call show_str
mov ax,4c00H ;程序返回
int 21H
dtoc: ;功能:将给定的word型数据转为十进制字符形式,存入data段,首地址ds:si
;参数:ax 指定的word数据
;返回:ds:si指向data段字符串首地址
push bx; ;将子程序用到的寄存器压入栈
push cx;
push dx;
push si;
mov bx,000aH ;bl = 除数,bh = 一共除了几次
mov dx,0 ;即将进行除法,dx是高16位,低16位在ax中
pushyushu: ;每次循环都进行一次除法,将所得余数压入栈,直到某次除法后商为0
mov cx,0
mov cl,bl ;cx = 除数
call divdw ;调用不会溢出的除法函数,结果的商的高16位,在dx中,
;低16位在ax中,余数在cx中,此例中余数一定<10
push cx ;cx=余数,这个余数在显示的时候要倒序显示,因此先压入栈
inc bh ;记录将余数压入栈的次数
mov cx,dx ;cx = dx = 结果的商的高16位,这一步是为了判断dx是否为0
jcxz compareax ;若cx=0,则继续判断ax是否为0
jmp short pushyushu ;若cx!=0,则商不为零,返回循环体开头进行下一次除法
compareax: ;继续判断ax(除法的商的低16位)是否为0
mov cx,ax ;cx = ax = 结果的商的低16位,这一步是为了判断ax是否为0
jcxz popyushu ;若cx=0,则说明除法计算已经完毕,跳转下一步执行
jmp short pushyushu ;若cx!=0,则商不为零,返回循环体开头进行下一次除法
popyushu: ;商为0,除法结束
mov ch,0
mov cl,bh ;ch=0,所以cx = 将余数压入栈的次数,也就是接下来的循环次数
s1:
pop ax; ;从栈中pop出一个余数
add ax,30H ;从数字转为对应的数字字符
mov ds:[si],al ;用al就够了
inc si
loop s1
pop si ;子程序结束,将寄存器的值pop出来
pop dx;
pop cx;
pop bx;
ret
divdw: ;功能:计算word型被除数与byte型除数的除法
;参数: ax=被除数低16位,dx=被除数高16位,cx = 除数
;返回: ax=商的低16位,dx=商的高16位,cx = 余数
;计算公式: X/N = int( H/N ) * 65536 + [rem( H/N) * 65536 + L]/N
;其中X为被除数,N为除数,H为被除数的高16位,L为被除数的低16位,
;int()表示结果的商,rem()表示结果的余数。
push bx ;bx是额外用到的寄存器,要压入栈
mov bx,ax ;bx=L
mov ax,dx ;ax=H
mov dx,0 ;dx=0
div cx ;计算H/N,结果的商即int(H/N)保存在ax,余数即rem(H/N)保存在dx
;接下来要计算int(H/N)*65536,思考一下,65536就是0001 0000 H,
;因此计算结果就是,高16位=int(H/N),低16位为0000H。
push ax ;将int(H/N)*65536结果的高16位,即int(H/N),压入栈
mov ax,0
push ax ;将int(H/N)*65536结果的低16位,即0000H,压入栈
;接下来要计算 rem(H/N)*65536 ,同理可得,
;计算结果为 高16位= rem(H/N)*65536 ,即此时dx的值,
;低16位为 0000H。
mov ax,bx ;ax = bx = L ,即 [rem(H/N)*65536 + L]的低16位
div cx ;计算 [rem( H/N) * 65536 + L]/N ,结果的商保存在ax,余数保存在dx
;接下来要将两项求和。 左边项的高、低16位都在栈中,
;其中高16位就是最终结果的高16位,低16位是0000H。
;右边项的商为16位,在ax中,也就是最终结果的低16位,
;余数在dx中,也就是最终结果的余数。
mov cx,dx ;cx = 最终结果的余数
pop bx ;cx = int(H/N)*65536结果的低16位,即0000H。
pop dx ;bx = int(H/N)*65536结果的高16位,即最终结果的高16位
pop bx ;还原bx的值
ret
show_str:
;功能:将data段中首地址为ds:si的字符,以指定颜色显示在屏幕指定位置
;参数:dh 行号, dl 列号 ,cl 颜色
;返回:无
push dx ;将子程序用到的寄存器压入栈
push si
push ax
push bx
push es
mov ax,0B800H ;设置es为显示区段地址
mov es,ax
mov ax,00A0H ;设置首字符显示的地址
mul dh
mov dh,0
add ax,dx
add ax,dx
mov bx,ax ;bx是显示区的偏移地址
mov al,cl ;用al存储属性字节
mov ch,0
mov si,0
s: ;循环读取字符并显示
mov cl,ds:[si]
jcxz ok ;若读到0,就退出循环
mov es:[bx],cl
inc bx
mov es:[bx],al
inc bx
inc si
jmp short s
ok: ;将寄存器的值pop出来
pop es
pop bx
pop ax
pop si
pop dx
ret ;返回
code ends
end start
总结
本文是王爽老师《汇编语言》(第四版) 第十章 实验10 编写3个子程序 的分析及代码。这个实验任务要按顺序完成,因为后边的任务会用到前边写的子程序。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)