簡體   English   中英

將遞歸轉換為匯編

[英]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函數生成的匯編語言代碼。 從這里開始,您需要做兩件事:

  1. 瀏覽代碼並確保您了解每條指令的作用,編譯器發出的原因以及函數的整體工作原理。 跟蹤代碼,就好像您是執行它的計算機一樣,確保您理解它。

  2. 在它周圍寫一個包裝器,包括匯編器通常需要的東西,比如section .text和入口點。

請注意,遞歸是簡單地通過調用實現parcours從內再次功能parcours功能。 這里C ++和匯編之間的唯一區別是C ++使用parcours(v, n, 2*k+1); ,而程序集使用一系列push指令(對於參數),然后call parcours

與您編寫的代碼相比,此代碼有何不同?

好吧,對於初學者,正如評論中所討論的那樣,它遵循一個標准的調用約定 ,其中非易失性寄存器保存在頂部(帶有一系列push指令)並在底部恢復(帶有相應的一系列pop指令) 。 參數以標准方式從右到左傳遞到堆棧上。

此外,優化編譯器(毫不奇怪)更好地生成最佳代碼序列。 特別是,他們知道使用enterleave創建/銷毀堆棧幀的速度很慢,因此他們直接操作堆棧指針( esp ),並且只在需要時執行。 作為另一個例子,他們知道shl reg, 1相當於add reg, reg ,但后者更快。 他們知道可愛的小動作像lea eax, [ebx+1]為1增添價值的有效方式ebx而在存儲結果eax ,而無需修改ebx

但最重要的是,它們具有簿記算法,可以在執行遞歸函數調用時保持堆棧的正確平衡,這是您在嘗試時無法正確執行的操作,這是導致分段錯誤的原因。

我將讓您對C ++編譯器生成的嘗試進行詳細的逐行比較。 這是學習如何編寫匯編代碼的一種非常有效的方法。 就像使用調試器逐步完成代碼一樣,可以准確地看到它從軌道上移開的位置。 現在嘗試它並不太晚,並將結果與​​這個正確的代碼進行比較。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM