[英]Translating recursivity to assembly
我試圖在匯編中翻譯C ++遞歸代碼。 我在32位模式下工作。
這是C ++代碼。
1 extern "C" void output (unsigned);
2 extern "C" void parcours (unsigned[], unsigned, unsigned = 0);
3
4 void parcours (unsigned v[], unsigned n, unsigned k) {
5 while (k < n) {
6 output(v[k]);
7 parcours(v, n, 2*k+1);
8 k = 2*k+2;
9 }
10 }
這是我試圖翻譯它。 我正在努力將遞歸C ++(或任何其他高級語言)轉換為匯編。 如果你能糾正這個代碼並指出我的錯誤,我將不勝感激。
%include "io.inc"
section .text
global start
start:
mov edi, [ebp+8] ;address of the first element of the array
mov eax, [ebp+12] ;n
mov edx, [ebp+16] ;k
parcours:
enter 0,0 ;stack frame creation
push edx ;we save k before the modification
shl edx,1
add edx,1 ;2*k+1
loop:
cmp edx,eax
jb endloop ;if k < n
push [edi+edx] ;we push for the output
call output
pop ebx
call parcours
shl edx,1 ;2*k
add edx,2 ;2*k+2
jmp loop
endloop:
pop edx
leave ;stack frame destruction
ret
碰巧的是,有專門為將C ++代碼轉換為匯編而設計的工具。 這些工具被稱為“編譯器”,這實際上是它們的主要目的。
您所做的是通過編譯器運行C ++代碼,以及使其為您提供中間匯編語言表示的相應標志。 在Gnu風格的編譯器中,如GCC和Clang,那將是-S
( -fverbose-asm
也很有幫助)。 對於Microsoft編譯器,選項是/FA
。
或者,您可以編譯代碼,然后使用objdump
(Gnu)或dumpbin
(MS)之類的工具來顯示目標代碼的匯編列表。 (如果使用調試符號進行編譯,這具有在匯編指令中交錯相應C ++語句的良好效果,至少是編譯器的最佳能力,因為不一定是1對1映射,更不用說用於優化目的的指令重新排序問題)。
要么具有相同的效果。 你轉過來:
extern "C" void output (unsigned);
extern "C" void parcours (unsigned[], unsigned, unsigned = 0);
void parcours (unsigned v[], unsigned n, unsigned k) {
while (k < n) {
output(v[k]);
parcours(v, n, 2*k+1);
k = 2*k+2;
}
}
進入這個:
parcours:
push edi
push esi
push ebx
mov esi, DWORD PTR [esp+20]
mov ebx, DWORD PTR [esp+24]
mov edi, DWORD PTR [esp+16]
cmp ebx, esi
jnb .L1
.L3:
sub esp, 12
push DWORD PTR [edi+ebx*4]
add ebx, ebx
call output
lea eax, [ebx+1]
add esp, 12
add ebx, 2
push eax
push esi
push edi
call parcours
add esp, 16
cmp esi, ebx
ja .L3
.L1:
pop ebx
pop esi
pop edi
ret
這是優化編譯器為parcours
函數生成的匯編語言代碼。 從這里開始,您需要做兩件事:
瀏覽代碼並確保您了解每條指令的作用,編譯器發出的原因以及函數的整體工作原理。 跟蹤代碼,就好像您是執行它的計算機一樣,確保您理解它。
在它周圍寫一個包裝器,包括匯編器通常需要的東西,比如section .text
和入口點。
請注意,遞歸是簡單地通過調用實現parcours
從內再次功能parcours
功能。 這里C ++和匯編之間的唯一區別是C ++使用parcours(v, n, 2*k+1);
,而程序集使用一系列push
指令(對於參數),然后call parcours
。
與您編寫的代碼相比,此代碼有何不同?
好吧,對於初學者,正如評論中所討論的那樣,它遵循一個標准的調用約定 ,其中非易失性寄存器保存在頂部(帶有一系列push
指令)並在底部恢復(帶有相應的一系列pop
指令) 。 參數以標准方式從右到左傳遞到堆棧上。
此外,優化編譯器(毫不奇怪)更好地生成最佳代碼序列。 特別是,他們知道使用enter
和leave
創建/銷毀堆棧幀的速度很慢,因此他們直接操作堆棧指針( esp
),並且只在需要時執行。 作為另一個例子,他們知道shl reg, 1
相當於add reg, reg
,但后者更快。 他們知道可愛的小動作像lea eax, [ebx+1]
為1增添價值的有效方式ebx
而在存儲結果eax
,而無需修改ebx
。
但最重要的是,它們具有簿記算法,可以在執行遞歸函數調用時保持堆棧的正確平衡,這是您在嘗試時無法正確執行的操作,這是導致分段錯誤的原因。
我將讓您對C ++編譯器生成的嘗試進行詳細的逐行比較。 這是學習如何編寫匯編代碼的一種非常有效的方法。 就像使用調試器逐步完成代碼一樣,可以准確地看到它從軌道上移開的位置。 現在嘗試它並不太晚,並將結果與這個正確的代碼進行比較。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.