将递归转换为汇编

时间:2017-05-25 14:08:42

标签: assembly x86 nasm

我试图在汇编中翻译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

1 个答案:

答案 0 :(得分:0)

恰好有一些专门为将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和入口点。

  3. 请注意,只需从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]这样可爱的小技巧是一种有效的方法,可以将结果存储在ebx而不修改eax的情况下,将{1}}的值加1。

    但最重要的是,它们具有簿记算法,可以在执行递归函数调用时保持堆栈正确平衡,这是您在尝试时无法正确执行的操作,这是导致分段错误的原因。

    我将让您对C ++编译器生成的尝试进行详细的逐行比较。这是学习如何编写汇编代码的一种非常有效的方法。就像使用调试器逐步完成代码一样,可以准确地看到它从轨道上移开的位置。现在尝试它并不太晚,并将结果与​​这个正确的代码进行比较。