退出整个递归堆栈

时间:2012-12-09 03:27:00

标签: c++ recursion

我从main()调用一个函数fooA,调用另一个递归的函数fooB。 当我想返回时,我继续使用exit(1)来停止执行。当递归树很深时退出的正确方法是什么?

通过递归堆栈返回可能没有用,因为返回通常会清除我构建的部分解决方案,而我不想这样做。我想从main()执行更多代码。

我读过可以使用Exceptions,如果我能获得代码片段会很好。

2 个答案:

答案 0 :(得分:12)

goto语句无法从一个函数跳回到另一个函数; Nikos C.是正确的,它不会考虑释放你所做的每个通话的堆栈帧,所以当你到达你转到的功能时,堆栈指针将是指向你刚刚进入的功能的堆栈框架......不,那只是不会工作。类似地,您不能简单地调用(直接或间接通过函数指针)您想要在算法完成时结束的函数。在进入递归算法之前,您永远不会回到您所处的环境。你可以想象用这种方式建立一个系统,但实际上每次你这样做都会导致泄漏。当前在堆栈上的内容(与泄漏堆内存不太一样,但效果类似)。如果你深入研究一种高度递归的算法,那么可能会出现大量的泄漏问题。堆栈空间。

不,你需要以某种方式返回到调用上下文。在C ++中只有三种方法可以这样做:

  1. 通过从每个函数返回其调用者来依次退出 通过有序的方式通过呼叫链备份。
  2. 抛出一个异常,然后在你之后抓住它 启动到您的递归算法(自动销毁) 堆栈中每个函数有序创建的任何对象 的方式)。
  3. 使用setjmp()& longjmp()做类似于throw&amp ;; 捕捉异常,但"投掷" longjmp()不会破坏 堆栈上的对象;如果任何此类对象拥有堆分配, 那些分配将被泄露。
  4. 要执行选项1,您必须编写递归函数,以便一旦到达解决方案,它就会返回某种指示,表明它已完成其调用者(可能是相同的函数),并且来电者看到了这个事实&通过返回到它的调用者(可能是相同的函数)来继续这个事实,依此类推,直到最后所有递归算法的堆栈帧都被释放并返回到递归中称为第一个函数的任何函数算法

    要执行选项2,您可以在try{...}中将调用包装到递归算法中,然后立即调用catch(){...}预期的抛出对象(可能是计算结果,或者只是一些让呼叫者知道的对象,嘿,我已经完成了,你知道在哪里可以找到结果")。例如:

    try
    {
        callMyRecursiveFunction(someArg);
    }
    catch( whateverTypeYouWantToThrow& result )
    {
        ...do whatever you want to do with the result,
        including copy it to somewhere else...
    }
    

    ...在您的递归函数中,当您完成结果时,您只需:

    throw(whateverTypeYouWantToThrow(anyArgsItsConstructorNeeds));
    

    做选项3 ......

    #include <setjmp.h>
    static jmp_buf jmp; // could be allocated other ways; the longjmp() user just needs to have access to it.
        .
        .
        .
    if (!setjmp(jmp)) // setjmp() returns zero 1st time, or whatever int value you send back to it with longjmp()
    {
        callMyRecursiveFunction(someArg);
    }
    

    ...在您的递归函数中,当您完成结果时,您只需:

    longjmp(jmp, 1); // this passes 1 back to the setjmp().  If your result is an int, you
                     // could pass that back to setjmp(), but you can't pass zero back.
    

    使用setjmp()/ longjmp()的坏处是,如果有任何堆栈分配的对象仍然存在&#34; alive&#34;当你调用longjmp()时,执行将跳转到setjmp()点,跳过这些对象的析构函数。如果您的算法仅使用POD类型,那么这不是问题。如果您的算法使用的非POD类型不包含任何堆分配(例如来自malloc()new),这也不是问题。如果您的算法使用包含堆分配的非POD类型,那么您只能使用选项1&amp; 2以上。但是如果你的算法符合使用setjmp()/ longjmp()的标准,并且你的算法在它完成的时候被埋在大量的递归调用之下,那么setjmp()/ longjmp()可能是最快的回归方式到初始调用上下文。如果这不起作用,选项1可能是你速度方面最好的选择。选项2看起来很方便(并且可能会在每次递归调用开始时消除条件检查),但与系统自动展开调用堆栈相关的开销有些重要。

    它通常会说你应该为特殊事件保留例外情况&#34; (事件预计非常罕见),以及与展开callstack相关的开销是原因。较旧的编译器使用类似于setjmp()/ longjmp()的东西来实现异常(在try&amp; catch的位置设置异常(setjmp())和{{1的位置的longjmp()但是,当然还有与确定堆栈中的哪些对象需要销毁相关的额外开销,即使没有这样的对象也是如此。此外,每次您遇到throw时,都必须保存上下文,以防try,如果异常真的例外事件,节省上下文所花费的时间被浪费了。较新的编译器现在更有可能使用所谓的“零成本例外”#34; (也就是基于表格的例外),这似乎可以解决所有世界的问题,但它并没有......它使正常的运行时间更快,因为不再需要每个都保存上下文您在throw上运行的时间,但是如果try执行,则现在甚至更多的开销与解码存储在大型表中的信息相关联,运行时必须进程以了解如何根据遇到throw的位置和运行时堆栈的内容来展开堆栈。因此异常并非免费,即使它们非常方便。你会在互联网上找到很多东西,人们会在这些东西上声称他们的代价是多么昂贵,以及他们减慢了代码的速度,你也会发现很多人反驳这些说法,双方都提供了坚实的数据来支持他们的主张。你应该从争论中得到的是,如果你期望它们很少发生,使用异常是很好的,因为它们会产生更清晰的界面。没有大量条件检查&#34; badness&#34;每次进行函数调用。但是你不应该使用异常作为调用者与其被调用者之间正常通信的手段,因为这种通信方式比简单地使用返回值要昂贵得多。

答案 1 :(得分:1)

在找到从二进制树的根到节点的路径时发生了这种情况。我正在使用堆栈来预先存储节点,并且递归不会停止,直到最后一个节点返回NULL。我使用了一个全局变量,整数i = 1,当我到达节点时我正在寻找我将该变量设置为0并使用while(i == 0)返回堆栈;允许程序返回内存堆栈而不关闭我的节点。