如何使用传递到当前子例程中的参数调用不同的子例程(用于递归)

How do I call a different subroutine using parameters passed into the current one (for use in recursion)?

本文关键字:子例程 调用 递归 用于 参数 何使用      更新时间:2023-10-16

我有两个函数,从输入中读取整数x和y。

产品退货 x * y

幂返回 x ^ y,但它使用递归和乘积来计算这一点。 所以 x 是"基数",y 是"指数"。

他们从C++打电话:

int a, b, x, y; 
a = product(x, y);
b = power(x, y);

这是 ASM。我让产品工作,但是在电源方面遇到了问题,因为我不确定从中调用产品的语法/方法/约定(并为递归调用自身(。编辑:必须使用递归。

    global product
    global power
    section .text
product:
    push  ebp       
    mov   ebp, esp  
    sub esp, 4  
    push edi    
    push esi    
    xor   eax, eax
    mov edi, [ebp+8]        
    mov esi, [ebp+12]   
    mov [ebp-4], edi        
product_loop:
    add [ebp-4], edi        
    mov eax, [ebp-4]                                    
    sub esi, 1          
    cmp esi, 1      
    jne product_loop        
product_done:
    pop esi         
    pop edi         
    mov esp, ebp    
    pop ebp         
    ret             
power:
    push  ebp       
    mov   ebp, esp  
    sub esp, 4  
    push edi    
    push esi    
    push ebx    
    xor   eax, eax  
    mov edi, [ebp+8]        
    mov esi, [ebp+12]       
    ;;;
check: 
    cmp   esi, 1            ; if exp < 1 
    jl  power_stop          
recursion:                  ; else (PLEASE HELP!!!!!!!!)
    ; eax = call product (base, (power(base, exp-1)) 
power_stop: 
    mov eax, 1              ; return 1 
power_done:
    push ebx        
    pop esi         
    pop edi         
    mov esp, ebp    
    pop ebp         
    ret     

编辑:我的解决方案!

power:
    ; Standard prologue
    push  ebp       ; Save the old base pointer
    mov   ebp, esp  ; Set new value of the base pointer
    sub esp, 4  ; make room for 1 local variable result
    push ebx    ; this is exp-1
    xor   eax, eax  ; Place zero in EAX. We will keep a running sum
            mov     eax, [ebp+12]           ; exp
            mov    ebx, [ebp+8]             ; base
            cmp     eax, 1                  ; n >= 1
            jge     L1                      ; if not, go do a recursive call
            mov     eax, 1                  ; otherwise return 1
            jmp     L2
    L1:
            dec     eax                     ; exp-1
            push    eax                     ; push argument 2: exp-1
            push    ebx                     ; push argument 1: base
            call    power                   ; do the call, result goes in eax: power(base, exp-1)
            add     esp, 8                  ; get rid of arguments
            push eax                        ; push argument 2: power(base, exponent-1)
            push ebx                        ; push argument 1: base
            call product                    ; product(base, power(base, exponent-1))
    L2:
    ; Standard epilogue
    pop ebx         ; restore register
    mov esp, ebp    ; deallocate local variables
    pop ebp         ; Restore the callers base pointer.
    ret             ; Return to the caller.

您使用的是 CDECL 调用约定,因此您必须首先向后推送堆栈中的参数,然后调用函数,然后在返回后清理堆栈。

         push   arg_last
         push   arg_first
         call   MyFunction
         add    esp, 8      ; the argument_count*argument_size

但这里有一些关于你的代码的注释:

  1. 您的函数product不返回任何值。在标签后立即使用mov eax, [ebp-4] product_done

  2. 乘法很容易通过指令mulimul进行。使用加法是最慢的方法。

  3. 通过递归计算功率并不是最好的主意。使用以下算法:

    1. Y = 1;

    2. 如果 N=0 退出。

    3. 如果 N 为奇数 -> Y = Y*x;N=N-1

    4. 如果 N 为偶数 -> Y = Y*Y;N=N/2

    5. 转到 2

使用SHR指令将 N 除以 2。使用test输入以检查奇数/偶数。

这样,您就不需要从函数调用product power

如果你不确定如何编写程序集,你通常可以用C++编写它并组装它以获取线索 - 如下所示:

int power(int n, int exp)
{
    return exp == 0 ? 1 :
           exp == 1 ? n :
           product(n, power(n, exp - 1));
}

然后,您应该能够使用gcc -S或任何编译器的等效开关进行程序集输出,或者如果您愿意,可以反汇编机器代码。

例如,上面的函数,用 int product(int x, int y) { return x * y; }int main() { return product(3, 4); } 抛出,用 Microsoft 的编译器 ala cl /Fa power.cc 编译:

; Listing generated by Microsoft (R) Optimizing Compiler Version 15.00.30729.01
        TITLE   C:homeanthonyuserdevpower.cc
        .686P
        .XMM
        include listing.inc
        .model  flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC  ?product@@YAHHH@Z                               ; product
; Function compile flags: /Odtp
_TEXT   SEGMENT
_x$ = 8                                                 ; size = 4
_y$ = 12                                                ; size = 4
?product@@YAHHH@Z PROC                                  ; product
; File c:homeanthonyuserdevpower.cc
; Line 1
        push    ebp
        mov     ebp, esp
        mov     eax, DWORD PTR _x$[ebp]
        imul    eax, DWORD PTR _y$[ebp]
        pop     ebp
        ret     0
?product@@YAHHH@Z ENDP                                  ; product
_TEXT   ENDS
PUBLIC  ?power@@YAHHH@Z                                 ; power
; Function compile flags: /Odtp
_TEXT   SEGMENT
tv73 = -8                                               ; size = 4
tv74 = -4                                               ; size = 4
_n$ = 8                                                 ; size = 4
_exp$ = 12                                              ; size = 4
?power@@YAHHH@Z PROC                                    ; power
; Line 4
        push    ebp
        mov     ebp, esp
        sub     esp, 8
; Line 7
        cmp     DWORD PTR _exp$[ebp], 0
        jne     SHORT $LN5@power
        mov     DWORD PTR tv74[ebp], 1
        jmp     SHORT $LN6@power
$LN5@power:
        cmp     DWORD PTR _exp$[ebp], 1
        jne     SHORT $LN3@power
        mov     eax, DWORD PTR _n$[ebp]
        mov     DWORD PTR tv73[ebp], eax
        jmp     SHORT $LN4@power
$LN3@power:
        mov     ecx, DWORD PTR _exp$[ebp]
        sub     ecx, 1
        push    ecx
        mov     edx, DWORD PTR _n$[ebp]
        push    edx
        call    ?power@@YAHHH@Z                         ; power
        add     esp, 8
        push    eax
        mov     eax, DWORD PTR _n$[ebp]
        push    eax
        call    ?product@@YAHHH@Z                       ; product
        add     esp, 8
        mov     DWORD PTR tv73[ebp], eax
$LN4@power:
        mov     ecx, DWORD PTR tv73[ebp]
        mov     DWORD PTR tv74[ebp], ecx
$LN6@power:
        mov     eax, DWORD PTR tv74[ebp]
; Line 8
        mov     esp, ebp
        pop     ebp
        ret     0
?power@@YAHHH@Z ENDP                                    ; power
_TEXT   ENDS
PUBLIC  _main
; Function compile flags: /Odtp
_TEXT   SEGMENT
_main   PROC
; Line 11
        push    ebp
        mov     ebp, esp
; Line 12
        push    4
        push    3
        call    ?power@@YAHHH@Z                         ; power
        add     esp, 8
; Line 13
        pop     ebp
        ret     0
_main   ENDP
_TEXT   ENDS
END

要引导您完成此操作:

?power@@YAHHH@Z PROC                                    ; power
; Line 4
        push    ebp
        mov     ebp, esp
        sub     esp, 8

以上是 power 函数的入口代码 - 只需调整堆栈指针以跳过函数参数,它将在下面以_exp$[ebp](即exp(和_n$[ebp](即 n (。

; Line 7
        cmp     DWORD PTR _exp$[ebp], 0
        jne     SHORT $LN5@power
        mov     DWORD PTR tv74[ebp], 1
        jmp     SHORT $LN6@power

基本上,如果 exp 不等于 0,我们将继续下面的标签 $LN5@power,但如果它是 0,则在 tv74[ebp] 处将1加载到堆栈上的返回值位置,并在 $LN6@power 处跳转到函数返回指令。

$LN5@power:
        cmp     DWORD PTR _exp$[ebp], 1
        jne     SHORT $LN3@power
        mov     eax, DWORD PTR _n$[ebp]
        mov     DWORD PTR tv73[ebp], eax
        jmp     SHORT $LN4@power

与上面类似 - 如果 exp 为 1,则将 n 放入 eax 中,然后从中放入返回值堆栈内存中,然后跳转到返回指令。

现在它开始变得有趣了...

$LN3@power:
        mov     ecx, DWORD PTR _exp$[ebp]
        sub     ecx, 1
        push    ecx

从 exp 中减去 1 并推入堆栈...

        mov     edx, DWORD PTR _n$[ebp]
        push    edx

同时将 n 推到堆栈上...

        call    ?power@@YAHHH@Z                         ; power

递归调用 power 函数,该函数将使用上面的两个值推送。

        add     esp, 8

返回上述函数后的堆栈调整。

        push    eax

将递归调用的结果(电源返回指令留在 eax 寄存器中(放到堆栈上...

        mov     eax, DWORD PTR _n$[ebp]
        push    eax

同时将 n 推到堆栈上...

        call    ?product@@YAHHH@Z                       ; product

调用 product 函数将调用上述power返回的值乘以 n

        add     esp, 8
        mov     DWORD PTR tv73[ebp], eax

product的结果复制到堆栈上的临时地址中。

$LN4@power:
        mov     ecx, DWORD PTR tv73[ebp]
        mov     DWORD PTR tv74[ebp], ecx

从该 tv73 临时位置获取值并将其复制到 tv74...

$LN6@power:
        mov     eax, DWORD PTR tv74[ebp]

最后,将 tv74 的product()结果移动到 eax 寄存器中,以便在product调用返回后方便快捷地访问。

; Line 8
        mov     esp, ebp
        pop     ebp
        ret     0

清理堆栈并返回。