王爽 汇编语言个人疑问汇总第七篇
第十章
实验 10 编写子程序
1. 显示字符串
原程序
assume cs:code
data segment
db 'Welcome to masm!',0
data ends
code segment
start:
mov dh,8 ; 第8行
mov dl,3 ; 第3列
mov cl,2 ; 颜色 2 的二进制是10,
; 参考书上实验9 根据材料编程对颜色的说明可得知
; 此处表示绿色
mov ax,data ; 代码段data指向ax寄存器
mov ds,ax ; (ds) = (ax)
mov si,0 ; 字符串的首地址
call show_str ; 调用子程序
mov ax,4c00h ; 固定的结束代码
int 21h ; 固定的结束代码
show_str: .
.
.
.
.
.
code ends
end start
最终代码
assume cs:code
data segment
db 'Welcome to masm!',0
data ends
code segment
start:
mov dh,8 ; 第8行
mov dl,3 ; 第3列
mov cl,2 ; 颜色 2 的二进制是10,
; 参考书上实验9 根据材料编程对颜色的说明可得知
; 此处表示绿色
mov ax,data ; 代码段data指向ax寄存器
mov ds,ax ; (ds) = (ax)
mov si,0 ; 字符串的首地址
call show_str ; 调用子程序
mov ax,4c00h ; 固定的结束代码
int 21h ; 固定的结束代码
show_str: ;其实这是一个子程序,里面是包含s和ok的,这样理解容易些
push ax
push bx
push dx
push cx
push es
push si ; 保存程序中用到的相关寄存器,对于这个程序中其实并没有使用到从栈中取出的寄存器数据,
; 个人觉得可能是一种编码习惯吧,放到栈中保护现场,以便后续的功能用到
mov al,160 ; 每行80个字符,所以每行占160个字节(参考参考书上实验9 根据材料编程对颜色的说明可得知), 160 和 下面dh的值都小于255,做8位乘法
mul dh ; (dh) = 8
mov bx,ax ; 160 * dh 运算结果在ax中 , 再赋值给bx ,计算的行的偏移量
mov al,2 ; 计算列的偏移量,每列占2个字节
mul dl ; (dl) = 2
add bx,ax ;计算出行和列加起来的偏移量,放在bx里
sub bx,2 ; 列号从0开始,最终结果要-2 才会显示在第3列
mov ax,0B800h ; 显示缓冲区固定地址
mov es,ax ;通过段寄存器es来操控要显示的内容,es:[bx]为要输入的显示区
mov al,cl ;先将cl中的颜色放在al
mov ch,0 ;cx高位ch为0,因为用不上
s:
mov cl,[si] ; 把代码段中的数据赋值给cx寄存器低位cl
jcxz ok ; 利用jcxz指令特性,cx=0时就会调用ok代码段,程序调用ok代码段,最后返回。
mov es:[bx],cl ; 低位存字符
mov es:[bx+1],al ; 高位存颜色
add bx,2 ; 往前偏移2个字节(1个字节存字符,1个字节存颜色),准备存下一个字符
inc si ; 下一个字符的偏移量
jmp short s ; 跳转到s代码段第一行指令
ok: ;返回
pop si ; 把前面放入栈中的寄存器,依次取出来,相当于还原了
pop es
pop cx
pop dx
pop bx
pop ax
ret
code ends
end start
- 运行结果
- 我们再来修改下显示的行和列,让它显示在第10行5列,就能体会到子程序的妙用了。
- 可以看出,我们简单修改下参数就能控制字符串的显示了,相当于
show_str
就是一个封装起来的函数,调用方不用管具体逻辑,传参就可以了。
程序分析
mov cl,2
,这是颜色 2 的二进制是10,参考书上实验9 根据材料编程对颜色的说明可得知,此处表示绿色。show_str
中有保存基础的操作,对于这个程序中其实并没有使用到从栈中取出的寄存器数据,个人觉得可能是一种编码习惯吧,放到栈中保护现场,以便后续的功能用到。- 每行
160
个字节,; 每行80个字符,所以每行占160个字节(参考参考书上实验9 根据材料编程对颜色的说明可得知)。 - 显示字符串的偏移量
bx
,列号从0开始,还要减2个字节,不然是在第4列开始。 - 利用
jcxz
指令特性,cx=0时就会调用ok代码段,程序调用ok代码段,最后返回。
本题参考
2. 解决除法溢出的问题
程序分析
- 按照小甲鱼视频中对
X/N = int(H/N)*65536+[rem(H/N)*65536+L]/N
公式的分析。他以328/2
为例:
- 把328分成2段,高位是
3
,低位是28
。 3/2
,等于1,余下1
。- 然后
128/2
等于64
。 - 最后商的高位是1,低位是64,余数是0。
- 下面用代码实现这个算法。
最终代码
assume cs:code
code segment
start:mov ax,4240h
mov dx,000fh
mov cx,0ah
call divdw
mov ax,4c00h
int 21h
divdw: push ax ; 压入L,先对H进行运算
mov ax,dx ; 被除数高位dx寄存器的值赋值给ax寄存器
mov dx,0 ; 清空dx寄存器,不影响余数位,使得高16为0,
div cx ; 执行后, ax存储结果的商,dx存储结果的余数
pop bx ; 先取出L,赋值给bx寄存器
push ax ; 再把运算出H/N 的商,压入栈中
mov ax,bx ; 此时ax为L
div cx ; L / N ,注意:16位除法的时候默认被除数dx为高16位,ax为低16位 ,
; 相当于 [rem(H/N)*65536+L]/N
;执行后, ax存储结果的商,dx存储结果的余数
mov cx,dx ; 把 L / N 的 余数赋值给 cx寄存器
pop dx ; 取出高位/除数的商,赋值给dx
ret ; 返回
code ends
end start
本题参考
https://blog.csdn.net/weixin_44223946/article/details/108906074
https://www.bilibili.com/video/BV1Rs411c7HG?p=52
3. 数值显示
程序分析
- 显示字符串的功能,我们几乎完全可以复用前面第2题的程序。
- 要注意,显卡遵循ASCII编码,我们要得到每一位对应 ASCII码值,
ASCII码值 = 十进制数码值 + 30H
。先求每位的10进制数,也就是实现下图的功能。
- 得到每一位的10进制数 + 30H
完整代码
- 下面
dtoc
子程序。 就是实现得到每位ASCII码
,并存放在data数据段
中。
assume cs:code,ds:data
data segment
db 10 dup (0)
data ends
code segment
start:
mov ax,12666
mov bx,data
mov ds,bx ; ds段寄存器指向data数据段
mov si,0
call dtoc ; 转为ASCII码的子程序,并且存在数据段中,操作ds段寄存器就可以放进去了
mov dh,8 ; 第8行
mov dl,3 ; 第3列
mov cl,2 ; 颜色 2 的二进制是10,
; 参考书上实验9 根据材料编程对颜色的说明可得知
; 此处表示绿色
;mov ax,data ; 代码段data指向ax寄存器 ,这2段代码在此程序中可以不要了
;mov ds,ax ; (ds) = (ax)
mov si,0 ; 字符串的首地址
call show_str ; 调用子程序
mov ax,4c00h ; 固定的结束代码
int 21h ; 固定的结束代码
dtoc:
push dx
push cx
push ax
push si
push bx ; 记录之前寄存器的数据
mov bx,0 ; bx用于记录有多少个数,作为s3代码段的循环次数,真正数据暂存在栈中
s1:
mov cx,10 ; 除数
mov dx,0 ; 先把余数置为0,避免影响计算
div cx ; 除以10
mov cx,ax ; 商赋值给cx寄存器
jcxz s2 ; 如果前面商为0,即cx=0,那么就会跳到s2代码段第一行指令
add dx,30H ; 余数 + 30h, ASCII码值 = 十进制数码值 + 30H
push dx ; ASCII码值暂存到栈中
inc bx ; 记录有多少个数
jmp short s1 ; 跳转到s1代码段第1行指令
s2:
add dx,30H ; 最后的余数 + 30H
push dx ; 放入栈中
inc bx ; 计数+1
mov cx,bx ; 计算赋值给cx寄存器,下面的loop使用,循环次数
mov si,0 ; 从0开始,ds段寄存器的位置
s3:
pop ax ; 取出栈中数据给ax寄存器
mov [si],al ; 意为 ds:[si] ,al
inc si ; 累加1,数据段下一个字节
loop s3 ; 循环
okay:
pop bx
pop si
pop ax
pop cx
pop dx ; 把之前数据依次取出
ret ; 返回
show_str: ;其实这是一个子程序,里面是包含s和ok的,这样理解容易些
push ax
push bx
push dx
push cx
push es
push si ; 保存程序中用到的相关寄存器,对于这个程序中其实并没有使用到从栈中取出的寄存器数据,
; 个人觉得可能是一种编码习惯吧,放到栈中保护现场,以便后续的功能用到
mov al,160 ; 每行80个字符,所以每行占160个字节(参考参考书上实验9 根据材料编程对颜色的说明可得知), 160 和 下面dh的值都小于255,做8位乘法
mul dh ; (dh) = 8
mov bx,ax ; 160 * dh 运算结果在ax中 , 再赋值给bx ,计算的行的偏移量
mov al,2 ; 计算列的偏移量,每列占2个字节
mul dl ; (dl) = 2
add bx,ax ;计算出行和列加起来的偏移量,放在bx里
sub bx,2 ; 列号从0开始,最终结果要-2 才会显示在第3列
mov ax,0B800h ; 显示缓冲区固定地址
mov es,ax ;通过段寄存器es来操控要显示的内容,es:[bx]为要输入的显示区
mov al,cl ;先将cl中的颜色放在al
mov ch,0 ;cx高位ch为0,因为用不上
s:
mov cl,[si] ; 把代码段中的数据赋值给cx寄存器低位cl
jcxz ok ; 利用jcxz指令特性,cx=0时就会调用ok代码段,程序调用ok代码段,最后返回。
mov es:[bx],cl ; 低位存字符
mov es:[bx+1],al ; 高位存颜色
add bx,2 ; 往前偏移2个字节(1个字节存字符,1个字节存颜色),准备存下一个字符
inc si ; 下一个字符的偏移量
jmp short s ; 跳转到s代码段第一行指令
ok: ;返回
pop si ; 把前面放入栈中的寄存器,依次取出来,相当于还原了
pop es
pop cx
pop dx
pop bx
pop ax
ret
code ends
end start
- 运行结果