《汇编语言》- 读书笔记 - 实验 10 编写子程序

在这次实验中,我们将要编写3个子程序,通过它们来认识几个常见的问题和掌握解决这些问题的方法。同前面的所有实验一样,这个实验是必须独立完成的,在后面的课程中,将要用到这个实验中编写的3个子程序。

1. 显示字符串

问题

显示字符串是个常用功能,写个子程序来实现此功能。子程序要支持参数:显示的位置(行、列)、内容和颜色。

子程序描述 show_str

名称: show_str
功能: 在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数: (dh) = 行号(取值范围 0~24),
(dl) = 列号(取值范围 0~79),
(cl) = 颜色,ds:si 指向字符串的首地址
返回:
应用举例: 在屏幕的83列,用绿色显示data段中的字符串

提示

  1. 子程序的入口参数是屏幕上的行号和列号,注意在子程序内部要将它们转化为显存中的地址,首先要分析一下屏幕上的行列位置和显存地址的对应关系;
  2. 注意保存子程序中用到的相关寄存器;
  3. 这个子程序的内部处理和显存的结构密切相关,但是向外提供了与显存结构无关的接口。通过调用这个子程序,进行字符串的显示时可以不必了解显存的结构,为编程提供了方便。在实验中,注意体会这种设计思想。

结果

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		; 颜色
		mov ax,data		; 设置数据段
		mov ds,ax
		mov si,0		; 指向字符串首地址
		call show_str	; 调用子程序
		
		mov ax,4c00h	; 退出
		int 21h
; ------------------- 子程序 -------------------
show_str:				
		push si				; 缓存寄存器
		push di
		push es
		push dx
		push cx
		push bx
		push ax

		mov ax,0B800h	; 设置显存段地址
		mov es,ax
		
		mov al,160		; 先算行偏移
		dec dh				; 行号从 0 开始,所以这里要先减1
		mul dh				; 行数 x 160算出行偏移
		mov bx,ax			; 行偏移先存到 bx
		mov al,2		; 再算列偏移
		dec dl				; 列号从 0 开始,所以这里要先减1
		mul dl
		add bx,ax		;+= 算出目标字符串(显存)的开始位置

		mov dl,cl 		; 字符属性换个地方存。cx下面要用来循环
		mov ch,0		; cx高位清空(后面我们只改 cl,就方便 jcxz 判断了)
		
		mov di,0		; 指向目标字符串首字符
		
 sloop:	mov cl,ds:[si]		; 取字符
		mov es:[bx+di],cl	; 字符写入显存
		mov es:[bx+di+1],dl ; 字符属性写入显存
		jcxz ok				; 如果为 0 字符结束,跳出循环
		inc si				; si 递增,源字符串指向下一字符
		add di,2			; di += 2,目标字符串指向下一字符
		jmp short sloop
	ok:	
		pop ax			; 还原寄存器
		pop bx
		pop cx
		pop dx
		pop es
		pop di
		pop si
		ret
; ------------------- 子程序 -------------------
code ends
end start

演示

在这里插入图片描述

2. 解决除法溢出的问题

问题

div指令执行除法时:
8位除法的AL = 商AH = 余数
16位除法则分别用AXDX存储余数
但若结果超出ALAX的最大范围,该如何处理?

比如:
8位除法:FFFF ÷ 1al 中容纳不下商 FFFFh
16位除法:10000h ÷ 1ax 中容纳不下商 1 0000h

为解决这一问题,我们设计一个子程序来专门处理它。

子程序描述 divdw

名称: divdw
功能: 进行不会产生溢出的除法运算,被除数dword 型,除数word 型,结果dword 型。
参数: (ax) = dword被除数 16 位,
(dx) = dword被除数 16 位,
(cx) = 除数
返回: (ax) = 结果的低16位
(dx) = 结果的高16位
(cx) = 余数
应用举例: 计算:1000000/10(F4240H/0AH)
结果:(dx)=0001H, (ax)=86A0H, (cx)=0

提示

书中给出的公式:

公式 X/N = int(H/N) * 65536 + [ rem(H/N) * 65536 + L ] / N
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

这个公式将可能产生溢出的除法运算:XN,转变为多个不会产生溢出的除法运算。公式中,等号右边的所有除法运算都可以用 div 指令来做,肯定不会导致除法溢出。
(关于这个公式的推导,有兴趣的读者请参看附注5。)

结果

assume cs:code
code segment
 start:	mov ax,4240h	; dword 被除数的低 16位
		mov dx,000Fh	; dword 被除数的高 16位
		mov cx,0Ah		; word  除数
		call divdw		; 调用子程序 dword 类型除法
		
		mov ax,4c00h	; 退出
		int 21h
; ------------------- 子程序 -------------------
divdw:
		push si		; 缓存寄存器
		push bx

		mov si,ax	; 因为要用到 ax,所以被除数的低 16位,临时放在 si
		mov ax,dx	; 先算高 16 位
		mov dx,0		;160
		div cx			; 除数16位,则被除数32位。商ax,余dx
		mov bx,ax		; 临时保存高16位商
		
		mov ax,si	; 后算低 16;16位的余数留在 dx不用动。相当于:余数 * 10000 +16位
		div cx			; 除数16位,则被除数32位。商ax,余dx
		mov cx,dx	; 得到余数
		mov dx,bx	; 拿回临时保存的高16位商
	
		pop bx		; 还原寄存器	
		pop si
		ret
; ------------------- 子程序 -------------------
code ends
end start

演示

在这里插入图片描述

3. 数值显示

问题

编程,将 data 段中的数据以十进制的形式显示出来。

data seqment
	dw 123,12666,1,8,3,38
data ends

但是数据在内存中都是二进制信息,
比如 12666 在内存保存的是二进制的0011 0001 0111 1010等于16进制317A

可见,要将数据用十进制形式显示到屏幕上,要进行两步工作:

  1. 将用二进制信息存储的数据转变为十进制形式的字符串。(我们需要设计一个子程序)
  2. 显示十进制形式的字符串。(这个我们可以前面设计 好的 show_str

子程序描述 dtoc

名称: dtoc
功能: word 型数据转变为表示十进制数的字符串,字符串以0为结尾符。
参数: (ax) = word 型数据
ds:si 指向字符串的首地址
返回:
应用举例 编程,将数据12666十进制的形式在屏幕的8行3列,用绿色显示出来。
在显示时我们调用本次实验中的第一个子程序 show str

提示

  1. 十进制数码字符对应的ASCII码 = 十进制数码值 + 30H
    12666 对应:
1 2 6 6 6
31H 32H 36H 36H 36H
  1. 要求得 12666 的每一位是多少,可以用除法迭代取数:
    在这里插入图片描述
  2. 借助 jcxz 指令,用是否为 0 作为循环结束的条件。
  3. 按这个顺序取余得到的是 66621 最后我们还要把它翻转过来才是 12666

结果

assume cs:code
data segment
	db 10 dup (0)
data ends

code segment
 start:	
		mov bx,data		; 设置段地址
		mov ds,bx
						; 调用子程序 dtoc 将 word 转 10 进制数字
		mov ax,12666		; word 型数据
		mov si,0			; 循环变量从 0 开始
		call dtoc		
		
						; 调用子程序 show_str 在屏幕 83列用绿色显示
		mov dh,8
		mov dl,3
		mov cl,2
		call show_str
		
		mov ax,4c00h	; 退出
		int 21h
; ------------------- 子程序 dtoc -------------------
dtoc:
		push si		; 缓存寄存器
		push di
		push bx
		
	; 用遍历取余的方式拿到 ascii 拿到的结果是倒的
	; 比如 12666 遍历取余拿到的是 66621 所以先丢栈里,方便下一步翻转
		mov bx,10
	s:	mov dx,0	; 除数 bx 是 16 位,则被除数是32位。高16位 dx 没有数据要补 0
		div bx		; 除完后 商在ax下次继续用,dx为余数
		add dx,30H	; 将余数转 ascii 
		push dx		; 入栈备用(此时高8位是无意义的,之后我们只要取低8位用即可 )
		inc si		; 循环变量 +1
		mov cx,ax	; 取商来判断是否为 0
		jcxz s_ok		; 如果商为0跳出循环
		jmp s			; 否则继续循环
 s_ok:
	; 目前栈里是 66621 顺序不是我们想要的,遍历一下出栈到 ds:[0]... 就正了
		mov di,0
	r:	pop ax
		mov [di],al	; 写入目标内存地址。第 di 个字符(我们只取低8位有效值)
		inc di
		dec si
		mov cx,si	; 出栈到第 si 个
		jcxz r_ok		; 如果 si 为 0 表示出完,结束循环
		jmp r			; 否则继续出栈
 r_ok:	
		pop bx		; 还原寄存器
		pop di
		pop si
		ret
; ------------------- 子程序 dtoc -------------------
; ------------------- 子程序 show_str -------------------
show_str:				
		push si				; 缓存寄存器
		push di
		push es
		push dx
		push cx
		push bx
		push ax

		mov ax,0B800h	; 设置显存段地址
		mov es,ax
		
		mov al,160		; 先算行偏移
		dec dh				; 行号从 0 开始,所以这里要先减1
		mul dh				; 行数 x 160算出行偏移
		mov bx,ax			; 行偏移先存到 bx
		mov al,2		; 再算列偏移
		dec dl				; 列号从 0 开始,所以这里要先减1
		mul dl
		add bx,ax		;+= 算出目标字符串(显存)的开始位置

		mov dl,cl 		; 字符属性换个地方存。cx下面要用来循环
		mov ch,0		; cx高位清空(后面我们只改 cl,就方便 jcxz 判断了)
		
		mov di,0		; 指向目标字符串首字符
		
 sloop:	mov cl,ds:[si]		; 取字符
		mov es:[bx+di],cl	; 字符写入显存
		mov es:[bx+di+1],dl ; 字符属性写入显存
		jcxz ok				; 如果为 0 字符结束,跳出循环
		inc si				; si 递增,源字符串指向下一字符
		add di,2			; di += 2,目标字符串指向下一字符
		jmp short sloop
	ok:	
		pop ax			; 还原寄存器
		pop bx
		pop cx
		pop dx
		pop es
		pop di
		pop si
		ret
; ------------------- 子程序 show_str -------------------
code ends
end start

演示

在这里插入图片描述

相关推荐

  1. 汇编语言实验五、子程序和宏

    2024-02-21 07:16:01       9 阅读
  2. Qt 多进程编程-将子程序嵌入到主窗口

    2024-02-21 07:16:01       36 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-21 07:16:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-21 07:16:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-21 07:16:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-21 07:16:01       20 阅读

热门阅读

  1. Spring Cloud Sleuth:分布式链路跟踪

    2024-02-21 07:16:01       23 阅读
  2. 服务器防火墙的应用技术有哪些?

    2024-02-21 07:16:01       27 阅读
  3. 使用ZooKeeper实现分布式锁

    2024-02-21 07:16:01       30 阅读
  4. socket与rpc的区别

    2024-02-21 07:16:01       34 阅读
  5. 开源Excel 处理工具库MyExcel介绍以及简单例子

    2024-02-21 07:16:01       28 阅读
  6. 大模型与大模型参数

    2024-02-21 07:16:01       25 阅读
  7. Python中Thop库的基本介绍和参数说明

    2024-02-21 07:16:01       34 阅读