ARM汇编实现记录10000以内素数并求和

前言

说到求素数这种问题,对于常写一些C/C++、Python或是Java的人应该觉得十分小儿科。但是你是否想过在底层的汇编中实现,还是相当简单吗?

一.判断是素数的方法

方法有很多,类似像判断一个数从2一直取值到根号下的该值中是否存在 (Python实现如下)

	#x为要判断的数
	end= int(x**(1/2))
	flag =0 #0为不是素数,1为是素数
	for i in range(end):
		if x%i ==0:
			flag=1
			break

但是我们应该很明显的感觉出来这样的方式在汇编当中不是很好的。别的不说根号这种东西在汇编当中要如何实现呢?想必是十分复杂的。

但是从中我们也是可以看出来的,有个步骤在判断是否是素数当中是必要的。那就是取余操作。本质上就是判断是否是倍数关系

二.改进方法

故而我们应该在汇编代码当中写出对应的判断是否是倍数的函数

但是随后如果不能取根号的我们,如果说只能从1到要判断的目标值一个个判断,如果是一个数两个数也就还好,但是到10000个数字一个一个判断的话简直时间复杂度爆炸。所以固然要采取一种简化的措施。

考虑到我们不但要求和,而且还要记录。这时候就有一个相对简化的算法非常合适了。

这个算法的本质就是:若一个数是质数,那么它一定不是任何一个比它小的质数的倍数。

三.完整思路:判断、记录、求和

那么既然上述的这个算法要求记录之前存的数据,那么实际上我们的需求就完成了。写代码之前先要理清大致思路。先用Python写出算法。(注:之所以用Python不用其他语言,主要考虑到的是其语法更为简单,语言包袱少)
Python代码的实现如下:

List = [] #用于放置素数
sum = 0
flag = 1
for i in range(2, 10+1): #从2一直到要求的数的闭区间
    for j in List: #对所有在要求数之内循环
        if i % j == 0: #为0
            flag = 0 #标志位置0
            break
    if flag == 1: #看看是不是一直到最后都没有发现一个倍数
        List.append(i) #是质数,放入列表中
        sum += i #和进行累加
    else: #否则再设置为1
        flag = 1 

那么类似就是这个大概的流程,主题框架就是两个内嵌的for 循环。

四.汇编实现

1.实现取整函数

思路是解决了但是实现有些困难的,主要是取余这个操作汇编里面是没有的,那么就要自己写了。

本质上是判断是否是倍数,那么就很简单了。利用最简单的思想:

要判断X1是否是X2的倍数,那么就把X1进行翻倍,看看是否能够变成X2就好了

Python实现基本思路:

num = X1
flag = 0
while True:
    if num == X2:
        flag = 1
        break
    num += X1

那么类似的汇编也就可以去写了,主要就是注意对于寄存器的分配
代码如下:

is_Multi PROC
	PUSH {R2-R12,LR} ;R0<R1
	MOV R2, R0 ;小一点的数值放入R2中,循环中将R2不断增加
	MOV R3,R0 ;R3记录R0的初值,函数内不改变
	MOV R0,#0 ;R0初始为0
loop 
	CMP R2,R1 ;看看是不是超出界限了
	BGT loop_end
	MOVEQ R0,#1
	ADD R2,R2,R3
	B loop
loop_end	
	POP {R2-R12,PC}
	ENDP
	END

2.完整质数求和函数的实现。

再看回顾一下之前写的Python思路代码

List = [] #用于放置素数
sum = 0
flag = 1
for i in range(2, 10+1): #从2一直到要求的数的闭区间
    for j in List: #对所有在要求数之内循环
        if i % j == 0: #为0
            flag = 0 #标志位置0
            break
    if flag == 1: #看看是不是一直到最后都没有发现一个倍数
        List.append(i) #是质数,放入列表中
        sum += i #和进行累加
    else: #否则再设置为1
        flag = 1 

我们的汇编当中没有列表这么高级的东西,那怎么办呢?用寄存器又存不下那么多个数。。

用存储器存不就完了吗

那么首先我们要先开拓一片存储器的空间了,这里唯一要注意的就是一定要开辟足够的空间

prime 
	SPACE 40000 

我们一个数要占一个字节,也就是4个字节,我们为了方便,给你开辟10000个数字的空间来装一万以内的质数肯定是绰绰有余的。

接下来就是可以直接上主函数和求和的函数了。最重要的就是寄存器的管理了,算法实际上已经十分清楚了。

代码如下:


__start	PROC ;主函数
	MOV R0,#10
	BL prime_sum
	B .
	ENDP


prime_sum PROC ;主要原理采用是,一个数不是比它小质数的倍数,那么它是质数
	PUSH {R1-R12,LR}
	;赋值部分
	MOV R2, R0 ;为了后面的is_Multi函数使用R0,故为了防止R0被覆盖,用R2记录下R0。R2为大循环中止条件。
	MOV R3, #0 ;用于记录当前存放数组的最大下标,shift,内存中此处值为空
	MOV R4, #0 ;用于记录所有质数的和
	MOV R5, #2 ;用于临时记录当前所要判断到的质数
	
loop_1 ;最外层大循环,用于遍历所有有可能是质数的数
	CMP R5, R2 ;是否超出范围
	BGT loop_1_end	
	MOV R6, #0 ;用于记录当前在内存的下标
loop_2 ;内层循环,用于判断第一个数是否为质数
	CMP R6, R3 ;看看当前的个数是否超出内存的范围
	BEQ loop_2_end
	LDR R7, =prime ; R6作为一个中间变量取出存储区的首地址
	MOV R8, R6 ,LSL #2  ;R8作为shift的正确地址型偏移。(实际上就是因为存储器内是以字节为单位的所以单纯的便宜啦要乘以4)
	ADD R8, R7,R8 ;此时R8真正要用的质数为真正的地址
	LDR R0, [R8] ;现在要判断的,已在存储器中的质数
	MOV R1, R5 
	BL is_Multi
	CMP R0,#1
	BEQ loop_2_end ;R0>0说明等于1,那么就不是质数
	ADD R6 , R6,#1 ;如果上一步没跳转就把要判断的下标加一	
	B loop_2 ;再返回
loop_2_end ;内层循环结束
	CMP R6,R3 ;比一下,看看是不是到最大值的时候还没找到对应的倍数跑出来的,如果是的话则相等为质数
	LDREQ R7, =prime ;取首地址
	MOVEQ R8, R3, LSL #2 ;该放数的位置找到,放到R8里面
	ADDEQ R8,R7,R8
	STREQ R5,[R8]; 放入数据
	ADDEQ R4,R5,R4 ;计算累加和
	ADDEQ R3,R3,#1 ;存储器最大位置+1
	ADD R5,R5,#1 ;让要判断的质数加一
	B loop_1
loop_1_end
	MOV R0, R4 ;结果返回至R0
	POP {R1-R12,PC}
	ENDP

实现的内容相对复杂,可以根据代码注释进行辅助理解。
重要的事情就是如果你没有过目不忘的记忆力,那么寄存器一定要标清楚。(要不然下次过一个星期再看,你根本不知道你写的是什么鬼。。尤其是汇编)

3.跟踪测试

如果实在很难看懂的话,那么如下内容应该会有些帮助。我将进行该程序整体的单步调试解读,重要的部分都会写出来。

1.首先我们可以看到SP的值与我们默认值有所不同,这是因为我们进行了空间的分配。 我这里不分配空间的默认值是0x20000200而现在变成了0x20009e40
在这里插入图片描述
用Python计算一下两个十六进制数,发现多出来的这个数就是我们所分配的40000个字节。说明我们所要的区域的地址就是在这个区间内。

不添加时默认有200个字节的区域,现在开辟了以后又多增加了40000个字节。也就是说总共有40200个字节供我们存储数据。
起始地址是0x20000000
在这里插入图片描述
2.运行第一轮外层循环之前(可以通过红箭头看到现在所运行到的位置),完成一些寄存器的初始化,也最主要的就是完成将R0的数值转到R2当中,因为后面的判断是否是倍数的函数要用到R0和R1两个寄存器。所以现在就先把对应的数值拿出来。
在这里插入图片描述
(以下将以每轮外层循环作为单位进行说明)
3.第一轮外层循环判断的是2,此时内存当中没有存储任何素数。运行结果如下。因为下图方块中判断相等的语句直接相等,不用取判断倍数关系。那么就判断出来是质数,跳出到后面执行是质数要进行的操作,也就是将R3是质数的个数要+1(已经存进去了),R4作为要求的质数的和,要加上2(因为它判断出来是质数,当然要加上去)。

我们在内存当中对应0x20000000这个位置也可以看到我们存储的2
在这里插入图片描述
2.第二轮外层循环的判断的是3是不是质数。

下图是第二轮循环中第一次运行到里循环这个位置当中时的状态。我们可以看到经过之前写的判断是否是倍数的函数后R0的可以反应出是否R1是R0的倍数,现在可以看出R0是0。经过方框中的程序时,发现经过与1进行比较发现不是倍数关系。根据之前所描述的思想那就需要继续比较下去那么就需要继续运行下去。
在这里插入图片描述
由于我们现在内存存储的素数也就只有一个2这个素数,所以第二轮循环又可以直接出去了,当然了它的R6和R3是相等的,与之前第一轮做的事情一样就可以,存储,累加,计数器增加。
在这里插入图片描述
可以看到,存储器内容增加了,和变为了5
在这里插入图片描述
3.第三轮循环是判断4是不是素数。很明显的他就是内存中的第一个数也就是2的倍数。经过is_Multi函数的判断R0的值变为了1,此时经过下一句BEQ之后将会直接进入到内层循环的结尾。
在这里插入图片描述
在结尾处的比较十分明显看的到二者不相等是小于的关系,所以自然下面的和EQ有关的操作直接不做,跳跃到下一个数5进行判断就好了
在这里插入图片描述

4.后面的过程大同小异,就是如上的一个流程,因为要判断的数过多,我们直接到完成以后的寄存器以及存储器的状态进行分析。在B . 处打一个断点就可以了。
(因为一万个数还是有些运算量的,运行要一些时间,别以为是出错了,其实人家在运行。。。)

可以看到R0就是对应求出的和为0x005787CC
在这里插入图片描述
可以转换为十进制就是5736396。
在这里插入图片描述
看一下存储器(太多了节选一下)可以看到从0x20000000开始从左到右存放了2 3 5 7 11 13 17 19…(人家是十六进制的别错认为是十进制的了)
在这里插入图片描述
最大一直存储到数字F5 26 00 00 当然了这个数字的正确读法是每两个16进制为一个单位,反向写出来的也就是0x000026F5也就是9973
在这里插入图片描述

4.源代码

所有代码如下所示,可直接运行,如若还是没懂,可以通过代码注释以及前面的实现思路对照理解。

Stack_size EQU  0x200	
	AREA  mSTACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem
	SPACE Stack_size  
__init_msp	

	AREA RESET,DATA, READONLY 
__Vectors
	DCD __init_msp 
	DCD __start		
	SPACE 0x400

	AREA xData,DATA, READWRITE
prime 
	SPACE 40000 ;为
prime_i
	AREA mCODE, CODE, READONLY

;prime是为质数所开辟的空间,获取其首地址的方式是=prime

__start	PROC
	MOV R0,#10000
	BL prime_sum
	B .
	ENDP

prime_sum PROC ;主要原理采用是,一个数不是比它小质数的倍数,那么它是质数
	PUSH {R1-R12,LR}
	;赋值部分
	MOV R2, R0 ;为了后面的is_Multi函数使用R0,故为了防止R0被覆盖,用R2记录下R0。R2为大循环中止条件。
	MOV R3, #0 ;用于记录当前存放数组的最大下标,shift,内存中此处值为空
	MOV R4, #0 ;用于记录所有质数的和
	MOV R5, #2 ;用于临时记录当前所要判断到的质数
	
loop_1 ;最外层大循环,用于遍历所有有可能是质数的数
	CMP R5, R2 ;是否超出范围
	BGT loop_1_end	
	MOV R6, #0 ;用于记录当前在内存的下标
loop_2 ;内层循环,用于判断第一个数是否为质数
	CMP R6, R3 ;看看当前的个数是否超出内存的范围
	BEQ loop_2_end
	
	LDR R7, =prime ; R6作为一个中间变量取出存储区的首地址
	MOV R8, R6 ,LSL #2  ;R8作为shift的正确地址型偏移。(实际上就是因为存储器内是以字节为单位的所以单纯的便宜啦要乘以4)
	ADD R8, R7,R8 ;此时R8真正要用的质数为真正的地址
	LDR R0, [R8] ;现在要判断的,已在存储器中的质数
	MOV R1, R5 
	BL is_Multi
	CMP R0,#1
	BEQ loop_2_end ;R0>0说明等于1,那么就不是质数
	ADD R6 , R6,#1 ;如果上一步没跳转就把要判断的下标加一	
	B loop_2 ;再返回
loop_2_end ;内层循环结束
	CMP R6,R3 ;比一下,看看是不是到最大值的时候还没找到对应的倍数跑出来的,如果是的话则相等为质数
	LDREQ R7, =prime ;取首地址
	MOVEQ R8, R3, LSL #2 ;该放数的位置找到,放到R8里面
	ADDEQ R8,R7,R8
	STREQ R5,[R8]; 放入数据
	ADDEQ R4,R5,R4 ;计算累加和
	ADDEQ R3,R3,#1 ;存储器最大位置+1
	ADD R5,R5,#1 ;让要判断的质数加一
	
	B loop_1
loop_1_end
	MOV R0, R4 ;结果返回至R0
	POP {R1-R12,PC}
	ENDP




is_Multi PROC
	PUSH {R2-R12,LR} ;R0<R1
	MOV R2, R0 ;小一点的数值放入R2中,循环中将R2不断增加
	MOV R3,R0 ;R3记录R0的初值,函数内不改变
	MOV R0,#0 ;R0初始为0
loop 
	CMP R2,R1 ;看看是不是超出界限了
	BGT loop_end
	MOVEQ R0,#1
	ADD R2,R2,R3
	B loop
loop_end	
	POP {R2-R12,PC}
	ENDP
	END

(以上内容如发现错误欢迎指正)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章