汇编实现寻找水仙花数,并保存在存储器中(分布实现版)

观前提醒,以下内容基于Cortex M4的STM32F407进行
代码使用keil进行仿真实现

任务:寻找1000以内的水仙花数,保存在存储器中

首先我们需要知道水仙花数是怎样计算出来的,先写出高级代码,然后转换成相应的汇编。

水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身。例如:1^3 + 5^3+ 3^3 = 153。

那么c语言层面如何进行实现,以下为实现代码:

void is_Narcissistic(int a){
	int sum = 0;
	//依次获取各位数
	int b = a;
	while(b%10!=0||b/a!=0){
		b = b%10;
		sum+=b*b*b;		
		b = a/10;
	}
}

实现起来不是很难,重要的是将其转换成汇编。
下面我们一步步来实现这个任务

汇编启动代码

编程第一步,就是永恒的hello world,所以现在让我们来看看怎样书写启动代码,执行汇编语句。

首先我们要知道当我们用高级代码进行编程的时候,去执行程序,都会经过

  1. 预处理
  2. 汇编:将高级语言转换成汇编代码
  3. 编译
  4. 链接

而程序所在的内存空间从高地址到低地址被分成堆栈,空洞,数据,代码段
所以我们首先要定义各个段来存放相应的数据。

首先是定义段的格式(不能顶格)

	AREA [段名],[段类型],[权限]	;定义一个数据段,可读写
标号					;这个就是段的起始地址
	SPACE 段大小		;开辟多大的空间
标号(表示结尾)

其中段类型,可以为CODE,DATA,STACK

另外,我们需要一个向量表,在linux中向量中用于记录发生中断时,处理中断的向量号,以及相应的地址,这里我们需要把定义的堆栈段,代码段记录在向量表中。

那么我们就根据程序段的为中,从高地址到低地址定义段

;定义宏,方便后期数值的修改
stack_size  EQU 0x200
vertor_size EQU 0x400

	AREA mystatck,DATA,READWRITE	;定义一个数据段,可读写
stack_s
	SPACE stack_size				;开辟一个大小为stack_size大小的空间	
stack_end	;stack top--SP(required)

	AREA RESET,DATA,READONLY		;name must be RESET
vector_s
	DCD stack_end					;the first must be SP(test)
	DCD code_start					;code start
	SPACE vertor_size				;开辟一个大小为vector_size大小的空间	
vector_end

	AREA mycode,CODE,READONLY,ALIGN=3	;ALIGN=3(required)
	PRESERVE8						;指定当前文件保持堆栈八字节对齐	

code_start
	B .								;表示跳转到code_start的位置继续执行,若不写这一句,
									;代码可能执行到我们不知道什么位置的位置
	END

所以,接下来的任务就是在code_start标号开始的位置来写汇编代码了。

任务1:保存数到存储器

由于我们的任务事先申明了要将找到的数保存在寄存器中,所以,我们需要再去定义一个数据段存放数据,这个段我们就写在代码段的前面,给定100字节的空间。

;define data
	AREA mydata,DATA,READWRITE
data_i
	SPACE 100

然后就是保存数到存储器。

这里我们回顾一下冯·诺依曼结构,有输入输出设备,存储器,运算器,控制器五个部分组成,例如我们想实现3+5的操作,首先控制器需要发送信号,从存储器找到3的地址,取出3,然后通过总线,将3放在寄存器中,5也是如此,然后运算器进行2个数的假发运算,计算的结果则通过总线写回存储器中。

看到了嘛,这个过程都是寄存器到存储器,或者存储器到寄存器的单向过程。

那么我们在汇编中,也是如此,如果要进行运算,肯定要把数存放在寄存器中,然后从寄存器写回存储器。

让我们来做一个实验,将寄存器中的数据写入存储器

	MOV R0,#1
	LDR R1,=data_i			;在R1寄存器中存放一个地址
	STR R0,[R1]				;从寄存器加载数据到存储器

在这里插入图片描述
那么我们就完成了这个基本任务,还需要学会一个知识点,就是在汇编里面写函数,因为code_start标号开始的部分,我们可以看作c里面的主函数,我们不会把所有的数据处理都放在主函数里面,同理,汇编里面也是。

在汇编写一个函数

首先我们要知道当我们调用一个函数时,我们需要保护现场,即将调用前的寄存器数据,下一条执行的指令压栈,等到函数返回的时候出栈,恢复现场,这里就要用到POP和PUSH指令

让我们看到下面的指令,将通用寄存器的数值压栈,那么LR是什么,他记录的是函数调用的返回地址,当我们调用完之后,将LR的数值弹栈到PC中,那么下一条执行的语句就是函数调用后的下一条需要执行的语句。

这这个保护现场,恢复现场是我们需要在函数中写的。

	PUSH {R1-R12,LR}

	POP {R1-R12,PC}

定义一个函数使用以下格式,你可能会疑惑,之前不是说R0也是通用寄存器,为什么他不用保护。

这里就要说到参数的使用了,我们都知道,一个函数的定义需要指定入口参数(多个),返回参数(只能1个),函数名,函数体。那么入口参数放在哪里呢,答案是,放在R0-R3中,依次存放函数的第1,2,3,4个参数,那么如果我们的参数大于4个怎么办 ?答案是放在栈中,不过现在我们不需要这么多参数,所以就不演示了

那么返回参数呢,我们规定放在R0中,这也解释了为什么不保护R0,因为如果R0被保护,这个函数的返回参数就会变成传过来的第一个参数。

函数名	PROC
	PUSH {R1-R12,LR}

	POP {R1-R12,PC}
	ENDP

下面,我们写一个数模10的函数,由于数在机器中是用二进制表示的,所以我们转换思路,取最后4位,如果数>9,那么模就是9,否则就是取到的结果。

其中的BIC指令的功能是让指定位变0。

因为我们要让结果与9比较,那么用到CMP指令,如果大于,则赋值R0为9,大于是Greater than,所以在MOV的基础上加上GT,表示大于的时候才执行这条指令。

cal_mod_ten PROC
	PUSH {R1-R12,LR}
	BIC R0,R0,#0xf<<28
	CMP R0,#9
	MOVGT R0,#9
	POP {R1-R12,PC}
	ENDP

循环语句怎么实现

由于我们需要找出1000以内的水仙花数,那么需要进行循环,从1到1000取数,分别调用判断函数。还记得GOTO语句嘛,直接粗暴的跳转表示法,汇编里面也是这个思路,循环体运行完,就跳转到循环体最开始,如果不满足条件,则跑到循环体最后,跳出循环

loop
	B loop_end		;出循环条件,一定要写,否则不能出循环
	
	B loop
loop_end

例如,做一个小实验,计算1+……100

	MOV R0,#1		;1
	MOV R1,#0		;sum
loop
	CMP R0,#100		
	BGT loop_end	;表示大于i>100就出循环
	ADD R1,R0,R1	;sum = sum+i
	ADD R0,R0,#1	;i=i+1
	B loop			;跑到循环开始
loop_end

水仙花数判断实现

首先,通用寄存器R0-R12是我们都可以使用的

其次,我们写汇编的时候,除非是大佬,不要一定要先写一个c找到思路,对照这写出汇编。

下面就是我写的水仙花数判断过程了

    ;define data
    	AREA mydata,DATA,READWRITE
    data_i
    	SPACE 100
    
    	IMPORT cal_thr				;由于三次方难以用汇编代码实现,故调用c函数
    	
    	AREA mycode,CODE,READONLY,ALIGN=3
    	PRESERVE8	
    			
    code_start PROC
    	MOV R0,#0
    	MOV R1,#1			;i
    	LDR R2,=data_i
    loop_p							;从2开始计算,1不算水仙花数?
    	ADD R1,#1			;i++
    	CMP R1,1000			;i<1000
    	BGT loop_p_end
    	MOV R0,R1
    	BL is_Narcissistic	
    	CMP R0,R1			;i==is_Narcissistic(i)
    	BNE loop_p
    	STR R1,[R2]			;保存到存储器
    	ADD R2,#4			;地址左移4
    	B loop_p
    loop_p_end
    	B .
    	ENDP
    
    is_Narcissistic PROC			;返回各位数三次方和计算结果
    	PUSH {R1-R12,LR}
    	MOV R3,#0		;sum = 0
    	MOV R2,R0		;b = a
    loop_n
    	BL cal_mod_ten		;i%10
    	CMP R0,#0			;i%10==0
    	BEQ OUT_FLAG
    	BL cal_thr			;(i%10)^3
    	ADD R3,R0			;sum+=(i%10)^3
    OUT_FLAG
    	MOV R0,R2
    	BL cal_div_ten		i/10==0
    	CMP R0,#0
    	BEQ loop_n_end
    	MOV R2,R0
    	B loop_n
    loop_n_end
    	MOV R0,R3
    	POP {R1-R12,PC}
    	ENDP
    	
    cal_div_ten PROC			;计算一个数除以10的商
    	PUSH {R1-R12,LR}
    	MOV R1,R0
    	MOV R0,#0			;sum
    loop_d
    	CMP R1,#10			;i<10
    	BLT loop_d_end
    	SUB R1,#10			;i-=10
    	ADD R0,#1			;sum+=1
    	B loop_d
    loop_d_end
    	POP {R1-R12,PC}
    	ENDP
    		
    cal_mod_ten PROC			;计算一个数模10的结果
    	PUSH {R1-R12,LR}
    loop_m
    	CMP R0,#10			;i<10
    	BLT loop_m_end
    	SUB R0,#10			;i-=10
    	B loop_m
    loop_m_end
    	POP {R1-R12,PC}
    	ENDP
    		
    	END

    extern int cal_thr(int);
    int cal_thr(int a){
    	return a*a*a;
    }

在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值