[从零学习汇编语言] - 模块化程序设计进阶

256 阅读5分钟

前言

点赞再看,养成习惯!

今天我们要做的就是通过编写几个小程序来加深对于模块化设计的理解。


一、 数据展示

1.1 需求

我们需要设计一个可以在屏幕的某个位置显示字符串

1.2 思路分析

比如我们要显示1234这个数字,我们首选要思考:

  1. 我们如何获得每一位的数字
  2. 怎么显示到显示屏

1.2.1 获取数字

我们现在说我们如何获取需要展示在屏幕上的数字。我们以字符1234举例,以往我们在存储数据的时候都会为其开辟一个数据段,这次我们也不例外:

assume  cs: code 
data segment

     dw  1234
data ends 

stack segment
     db 128 dup(0)
stack ends 

但是有个问题要强调下: 虽然我们在数据段里面存储了1234这四个参数, 但是其本质是存储的这四位数字的二进制串,只不过是我们使用注入dosbox这种编译器时会为我们自动转化为16进制数字。而如果我们想要将其展示到屏幕上时,就需要将其转化为字符才可以。说到字符,还记得之前我们有讲过的ASCII码表吗?这个时候我们就需要用到它(忘记什么是ASCII码表的同学请点击这里)。在这里我们需要做的就是将我们得到的十六进制参数加上特定的差值使其可以匹配到ASCII码表上对应的值。比如数字1,在ASCII码表上对应的十六进制值为31,那我们要做的就是将1+30H 即可。

1.2.2 如果显示在屏幕上

这个问题相对简单了一些,在我们使用dosbox虚拟机的时候,我们只需要将想要显示在屏幕的数据传送给显示区的起始地址b800即可。

1.3 源代码

assume  cs: code 
data segment

     dw  1234
data ends 

stack segment
     db 128 dup(0)
stack ends 

code segment 
     
	 main :     
				mov ax,data  ; 保存数据段
				mov ds,ax    ; 将数据段赋予 data segment寄存器
				mov si,0     ; 将source index 赋值为0
				mov ax,stack ; 保存栈段
				mov ss,ax    ; 将栈段信息保存到stack segment 寄存器
				mov sp,128   ; 调整stack pointer 栈顶指针
				mov bx,0B800H ; 将base 寄存器值赋予显存初试处
				mov es,bx    ;  将显存初始处保存在extra segment 辅助数据段寄存器
				mov di,160*15 ; 初始化 DI值 可自定义
				add di,40*2    ;同上
			
				mov ax,ds:[si] ; 将ds:si data segment数据段起始值赋予Accumulator寄存器
				mov dx,0      ; 恢复base寄存器初始值
				call str_show  ; 调用 str_show 方法
				mov ax,4c00h
				int 21h
 str_show : 
				mov cx,10      ; 使用cx寄存器暂时保存除数
				div cx         ; ax / cx      商保存在ax寄存器中 , 余数保存在 dx寄存器中
				add dl,30h     ; dl(data寄存器 低位寄存器) 增加30h 适配ASCII码表
				mov es:[di+0],dl ; extra segment 此时指向显存中, 将结果值展示在显存中
				mov byte ptr es:[di+1],00000011B ; 调整颜色
				mov cx,ax     ; 获取余数 
				jcxz shortDivRet ; 判断商是否为0,继续计算
				mov dx,0     ; 重置dx中保存的余数
				sub di,2   ;  data index数值减2
				jmp str_show ; 继续运行
		
shortDivRet: 	ret
code ends 
end main 
            
            

运行结果:

在这里插入图片描述

二、 解决除法溢出问题

让我们回忆一下div指令的使用规则:

  1. 当我们进行8位除法时,运行结果的商会保存在al寄存器中,余数保存在ah寄存器中。
  2. 当我们进行16位除法时,运行结果的商会保存在ax寄存器中,余数保存在dx寄存器中

但是如果我们进行8位除法,结果的商超过al寄存器的大小呢?比如:

assume cs:code 
code segment
	main:
				mov ax,1000H
				mov bh,1
	                      			div bh
				
code ends  
end main 

本段程序运行结果商为1000H,但是明显的1000H在al寄存器中无法放下。当我 们使用div指令时,出现这种情况是 很常见的。运算结果的商值太大,超过了寄存器所能储存的范围,当CPU执行DIV等除法指令时,如果发生这种情况,将引发一个CPU的内部错误:内存溢出 (Divide overflow),而我们接下来要做的事情就是通过特殊的程序来处理这个错误。

2.1 程序描述

我们需要设计一个程序使我们的除法运算不会产生溢出错误,我们的被除数类型为dword,除数为word,结果为dword

2.2 思路分析

这里有个通用的公式,我们按照公式实现即可: X/N = int(H/N) * 65536 + [rem(H/N) * 65536 +L]/N 其中X/N分别为除数和被除数,H为高32位,L为低32位

2.3 代码实现

assume cs:code 

data segment 
    dd   1000000
data ends 

stack segment stack
    db 128 dup(0)
stack ends 

code segment
	main:  mov ax,stack     ;获取栈段地址
		   mov ss,ax        
		   mov sp,128
		   mov ax,data      ;获取数据段
		   mov ds,ax
		   mov si,0          
		   mov ax,ds:[si]   ;低位寄存器
		   mov dx,ds:[si+2] ;高位寄存器
		   mov cx,10        ;保存除数
		   push ax          ;存储低位除数
		   mov bp,sp        ;保存低位除数指针
		   call long_div    ; 调用除法
		   
		   mov ax,4c00h
		   int 21h
long_div:  
		  mov ax,dx         ; 将高16位赋予ax寄存器
		  mov dx,0          ; dx清零
		  div cx            ; 计算结果   ax = int(H/N)  商存储在ax 余数存储在dx
		  push ax           ; 保存高位计算结果
		  mov ax,ss:[bp+0]  ; 获取低16位的值
		  div cx   			; 计算结果  
		  mov cx,dx         ; 获取余数
		  pop dx            ; 将高位运算结果赋予dx寄存器
		  ret 
code ends
end main
		   
		   
		   

结语

今天的内容就到此结束了,有疑问的小伙伴欢迎评论区留言或者私信博主,博主会在第一时间为你解答。

码字不易,感到有收获的小伙伴记得要关注博主一键三连,不要当白嫖怪哦~ 如果大家有什么意见和建议请评论区留言或私聊博主,博主会第一时间反馈的哦。