Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/loops/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Loops Can';t从递归循环堆栈溢出返回主进程_Loops_Assembly_Recursion_Masm_Irvine32 - Fatal编程技术网

Loops Can';t从递归循环堆栈溢出返回主进程

Loops Can';t从递归循环堆栈溢出返回主进程,loops,assembly,recursion,masm,irvine32,Loops,Assembly,Recursion,Masm,Irvine32,我试图编写一个简单的循环,它调用自己并跟踪循环次数。当我一步一步通过它时,一旦ECX在recursive PROC内点击0,它就会跳转到RET在L1内-我想在0之后它会返回到主程序 我的理解是,当调用一个过程时,它会将指令指针推到堆栈上,然后当调用RET时,它会将其弹出并返回到该点 ; Calls a recursive procedure ; INCLUDE Irvine32.inc .data constant DWORD 5 count DWORD ? .code main PRO

我试图编写一个简单的循环,它调用自己并跟踪循环次数。当我一步一步通过它时,一旦
ECX
recursive PROC
内点击0,它就会跳转到
RET
L1
内-我想在0之后它会返回到
主程序

我的理解是,当调用一个过程时,它会将指令指针推到堆栈上,然后当调用
RET
时,它会将其弹出并返回到该点

; Calls a recursive procedure
;

INCLUDE Irvine32.inc

.data

constant DWORD 5
count DWORD ?

.code
main PROC
    mov eax, 0
    mov ecx, constant
    CALL recursive
main ENDP

recursive PROC
    add eax, 1
    loop L1
    ret
    L1:
        CALL recursive
        ret
recursive ENDP

END main

当我将
RET
L1
中取出时,我会看到Visual S 2013中出现的“无可用源代码”页面。

如果我在
调用recursive
之后立即在
main
中打印
eax
的值,我会得到
5
,这样您的代码就会返回到
main
。问题是您的
main
缺少
ret
。即应改为:

main PROC
    mov eax, 0
    mov ecx, constant
    CALL recursive
    ret
main ENDP

由于您的程序应该在那里结束,因此您也可以使用Win32函数调用ExitProcess,0而不是
ret

初始答案的问题是,递归调用函数后,返回地址已被递归函数推送到堆栈上而隐藏。每次执行调用时,包含过程的返回地址都被推送到堆栈上,ESP被递减。如果我们一直递归地调用一个函数而不从中返回,堆栈将包含所有这些重复的地址和指向最后一次调用的ESP点

为了使ret指令有效,您需要从堆栈中弹出所有这些递归函数调用。您可以使用调试器验证此行为:

.386
.model flat,stdcall
.stack 4096

includelib  Irvine32.lib
includelib  User32.lib
include     Irvine32.inc

.code
main PROC
    mov eax,0
    mov ecx,5               ; # calls to perform
    call    recurs
    call    WriteInt

    INVOKE ExitProcess,0

main ENDP

; input   - ecx, parameter for  # function calls to perform
; uses ebx as workspace to dump junk from the stack 
recurs  PROC 
    loop    L1
    mov     ecx,eax     ; we need to keep track of recursive call count. 
L2: pop     ebx
    loop    L2
    inc     eax         ; add 1 for the initial call to made to recurs     
    ret                 ; ret pops the address currently pointed to by
L1: inc     eax         ; by ESP into EIP.         
    call    recurs      
recurs  ENDP            

END main

要返回到
main
,您必须执行与调用
CALL
s一样多的
RET
s。如果继续单步执行会发生什么情况?如果您只想执行ECX pop操作,
lea esp,[esp+ECX*4]
而不是在循环中执行
pop
。但这不是真正的递归;您基本上是在将其优化回循环中,但仍然使用
call
而不是
jmp
。它不是真正的递归,除非你用
ret
而不是
pop
通过
ret
返回一堆返回地址。(结构化编程,而不是一个
longjmp
一步退出所有这些范围。)此外,必须硬编码main传递到递归函数的递归深度这一事实完全使这种方法无效:它只适用于一个汇编时间常量输入值。如果您有一个包装器函数,它将初始参数保存在某个地方供以后使用,则可以认为这是有效的。此外,您的函数版本不会在函数条目上推送任何内容,只会推送返回地址,因此我认为
RECR\u COUNT*2
是不正确的;我不得不说,这是对
循环
-转发和
调用
的巧妙滥用,使递归和循环之间的某种东西在某种程度上起作用。在函数末尾有一个
ret
,这样
调用recurse
实际上可以返回而不会从末尾掉下来,向前的
循环
跳转实际上可以用作正确递归函数中的减量和基本大小写检查。谢谢Peter,是的,我在RECR\u COUNT*2上犯了一个错误,我有一个uses指令试图保存寄存器状态,因为已删除。好的,现在可以了,但它仍然不是递归。从堆栈中弹出一个返回地址而不返回它并不算。您将增加EAX以跟踪递归深度(而ECX将向基本情况倒计时),但是您没有通过递归返回,而是运行了一个愚蠢的循环,而不是
lea esp,[esp+eax*4}
放弃而不是使用返回地址。这就像接受异常或执行
longjmp
以从多个级别的嵌套函数调用中退出,有点像回到父范围。