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


前言

点赞再看,养成习惯!


回顾

还记得我们之前讲过的ret指令搭配call指令实现的子程序模板吗?忘记了没关系,我们可以再看一遍:

assume cs:code
code segment
main : 
      call sub1 ; 调用子程序sub1 ,也可以说是方法1
	  ...
	  mov ax,4c00h
	  int 21h
sub1: 
     ...
	 ret       ;返回主程序
code ends 
end main 

这个模板记住哦,等下要用的。


一、 模块化程序设计

1.1 子程序

如果屏幕前的你有其它语言的基础,那么子程序这个概念对于你来说是很好理解的。它就像是其它语言里的method,是为了处理主程序中某一块特定业务而存在的。这部分子程序通常都有以下的特点:

  1. 通常是可复用的,允许多个主程序重复使用的逻辑程序块。
  2. 一般如果某段特定逻辑需要向主程序隐藏实现细节,也可以使用子程序。
  3. 具有一定的独立性,相较于其他代码,更偏向于完成默写特定逻辑。

而子程序这个概念在不同的计算机语言中也会被称为不同的名称,比如函数程序方法等等都是属于子程序的一种。


1.2 子程序的参数和返回值

当我们知晓了子程序是为主程序处理特定业务场景的职责后,新的问题就接踵而至。由于子程序的代码段并没有功能主程序的数据段,因此我们需要为子程序提供特定的参数(形参),并且在子程序运算结束后还要将结果返回给主程序(返回值)。那么在这个前提下,我们接下来需要探讨的问题就很简单了,我们如何为子程序提供参数并存储返回值?
比如: 我们设计一个子程序用以计算N的三次方
思路剖析:

  1. 第一个关注点就是我们如何将N传递给子程序
  2. 第二个关注点是我们需要设计一个N的次方的逻辑代码
  3. 第三个关注点就是如何为主程序提供结果

那么这里面我们首先去做可以解决的问题:计算N的三次方的结果。 我们知道N的三次方就是将N自乘三次,很巧的是我们在上一章节曾经讲过如何进行乘法,那么这里我们先将这部分的代码设计出来。

assume cs:code
code segment
     mov ax,n
	 mov cx,3
cobe: mul ax
       loop cobe
code ends
end 

接下来我们需要解决的问题就是参数传递的问题了,首先按照我们已有的知识体系推断,数据一定会被存放进某个内存单元或寄存器,首先我们来考虑一下参数放到哪里合适呢?比如当前我们的需求只有一个参数,如果放到内存中也是要将内存地址放入寄存器中,这样就不如直接放到寄存器中简易。而可以存放通用数据的寄存器有四种:ax bx cx dx,其中cx又是状态寄存器,ax,dx要参与乘法运算,想来用bx寄存器是最合适的了。而返回值由于我们需要的就是乘法运算的结果,因此使用ax,dx即可。接下来我们只需要补充完整整个程序:

assume cs:code
code segment
	main:   mov bx,n ; n代表变量
	        call cube
	cube:   mov ax, bx 
	        mul ax
			mul ax
			ret 
code ends 
end 		

好,让我们来验证下我们的程序。让我们来定义一个data段,data段分为两组,第一组负责提供参数,第二组负责保存结果

assume cs:code
data segment
     dw 0,1,2,3,4,5,6,7
	 dd 8 dup(0)
data ends 

code segment
     main :   mov ax,data
	          mov ds,ax
			  mov si,0  ; 指向第一组内存单元 即变量
			  mov di,16 ; 指向第二组内存单元 即返回值
			  mov cx,8
 getAndSave:  mov bx,[si]
              call cube ; 进行立方运算
			  mov [di],ax
			  mov [di].2 ,dx
			  add si,2
			  add di,4
			  loop getAndSave
			  
			  mov ax,4c00h ;中断语句
			  int 21h

       cube:  mov ax,bx
	          mul ax
			  mul ax
			  ret
code ends
end main

结果验证:

在这里插入图片描述


1.3 批量数据的传递

那如果我们需要传递的参数不止一个呢?比如有3个?这个时候显然我们再想将参数放入到寄存器中是不合适的。在这种情况下,我们需要做的是将参数放入到内存中,然后将它们所在的内存空间的首地址放在寄存器中,传递给需要的子程序,对于具有批量数据的返回结果也可如此(用栈空间也可以)。让我们验证下:
设计一个程序,将一个全是字母的字符串转化为大写
这里我们需要关注两件事情:

  1. 字符串的内容和字符串的长度。这是因为我们需要告知子程序他们需要在内存中处理的数据的大小。假设我们设计的程序一次处理一个字符长度的字母,那我们完全可以使用loop循环进行大小写转换。这样的话我们可以将字符串长度存放到CX寄存器中。
  2. 还记得如何转换大小写么?如果忘记的同学请复习下逻辑运算章节

程序:

assume cs:code
data segment
    db 'conversation'
data ends

code segment
	 main:
			mov ax,data
			mov ds,ax
			mov si,0
			mov cx,12 ; 字符串长度为12
			call uppercase
			mov ax,4c00h
			int 21h
uppercase:  
			and byte ptr [si] ,11011111b ; 这里看不懂的同学请复习下逻辑运算章节
			inc si 
			loop uppercase
			ret 
code ends 
end main 

验证结果:

在这里插入图片描述


1.4 寄存器的冲突处理

我们先通过业务场景模拟一下冲突场面。现在有几段字符串,均是以0结尾,请将这些字符串转化为大写。
让我们先看下数据段:

assume cs:code
data segment
     db 'hello',0
	 db 'world',0
data ends

由于数据段都会以0结尾,因此我们不再需要将长度作为判断标准,只需要判断0即可。我们以此设计程序:

assume cs:code
data segment
     db 'hello',0
	 db 'world',0
data ends

code segment
     main: 
			mov ax,data
			mov ds,ax
			mov si,0
			call upperCase
			mov ax,4c00h
			int 21h
			
upperCase:  and byte ptr [si] ,11011111b
            inc si
			mov ch,0 ; 高位赋0,低位赋原有值 ,若原有值位0则自动中断循环
			mov cl,si
			loop upperCase
			ret 
code ends 
end main

但是到这里还是有个问题,我们的程序只能够处理完第一个字符串,但是第二个字符串还是没有办法处理。这是为什么呢?其实主要还是由于我们两个需求都太过依赖cx寄存器。有咩有什么改进的办法呢?其实也很简单,我们只需要将我们冲突的寄存器的值在冲突覆盖前保存下来,然后再冲突结束后再恢复即可。

assume cs:code
data segment
     db 'hello',0
	 db 'world',0
data ends

stack segment
    db 16 dup(0)
stack ends 

code segment
     main: 
			mov ax,data
			mov ds,ax
			mov si,0
			mov ax,stack
			mov ss,ax
			mov sp,16
			mov cx ,2
   change: 		
			push cx 
			call upperCase
			pop cx 
			loop change
			mov ax,4c00h
			int 21h
			
upperCase:  and byte ptr [si] ,11011111b
            inc si
			mov ch,0
			mov cl,[si]
			loop upperCase   
			ret 
code ends 
end main

这样对吗?还是不对,因为loop在判断cx = 0的时候会将cx-1 = FFFF ,此时不等于0,程序会进入死循环。我们该怎么办呢?很简单,用我们之前学到过的判断指令jcxz即可。

assume cs:code
data segment
     db 'hello',0
	 db 'world',0
data ends

stack segment
    db 16 dup(0)
stack ends 

code segment
     main: 
			mov ax,data
			mov ds,ax
			mov si,0
			mov ax,stack
			mov ss,ax
			mov sp,16
			mov cx ,2
   change: 		
			push cx 
			call upperCase
			pop cx 
			loop change
			mov ax,4c00h
			int 21h
			
upperCase:  and byte ptr [si] ,11011111b
            inc si
			mov ch,0
			mov cl,[si]
			jcxz return
			loop upperCase   
   return:	ret 
code ends 
end main

验证结果:
在这里插入图片描述

结语

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

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓龙oba

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值