前言
說到求素數這種問題,對於常寫一些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