【问题标题】:c++ recursion exits without obvious reasonc ++递归没有明显原因退出
【发布时间】:2011-08-28 02:35:31
【问题描述】:

我使用递归编写了一个函数。在测试它时,结果证明该函数在没有任何明显原因的情况下被终止,而递归仍在运行。

为了测试这一点,我写了一个无限递归。

在我的电脑上,这个函数在大约 2 秒后退出,最后输出大约是 327400。 最后一个数字并不总是相同的。

我使用 Ubuntu Lucid Lynx、GCC 编译器和 Eclipse 作为 IDE。如果有人知道问题出在哪里以及如何阻止程序退出,我会非常高兴。

#include <iostream>

void rek(double x){
    std::cout << x << std::endl;
    rek(x + 1);
}

int main(int argc, char **argv) {
    rek(1);
}

【问题讨论】:

  • 恭喜你第一次真正的 Stack Overflow!
  • 您好,谢谢您的回答。我预计会是这样。我只是对在这么少的调用后它已经达到堆栈大小感到困惑。
  • 顺便说一句,如果您有兴趣,我已经添加了一个答案,该答案通过尾调用优化显示了幕后发生的事情,因此您可以了解为什么不会发生堆栈溢出进行优化。

标签: c++ gcc ubuntu recursion infinite


【解决方案1】:

您很可能会溢出堆栈,此时您的程序将被立即终止。堆栈的深度总是会限制你可以递归的数量,如果你达到了这个限制,这意味着你的算法需要改变。

【讨论】:

  • 我认为这不太可能——尾调用优化可能会介入。
  • AFAIK 只有在使用正确的编译开关时才会发生。我相信 g++ 会是 -foptimize-sibling-calls,它包含在 -O2 和更高版本中。
【解决方案2】:

我认为您期望代码永远运行是正确的,如

中所述

How do I check if gcc is performing tail-recursion optimization?

如果 gcc 正在执行尾递归,您的代码应该能够永远运行。在我的机器上,它看起来像 -O3 实际上使 gcc 生成尾调用并实际上使堆栈变平。 :-)

我建议您将优化标志设置为 O2 或 O3。

【讨论】:

    【解决方案3】:

    您正在导致堆栈溢出(堆栈空间不足),因为您没有提供退出条件。

    void rek(double x){
       if(x > 10)
          return;
       std::cout << x << std::endl;
       rek(x + 1);
    }
    

    【讨论】:

      【解决方案4】:

      您是否希望它永远有效?

      不会的。在某些时候你会用完堆栈。

      【讨论】:

      • ... 除非编译器优化了尾递归,在这种情况下,它需要一段时间(永远)失败——如果x 变为+inf,那么“循环”可能会继续存在。
      • @Mat:嗯,我认为 2 秒实际上很长。 :)
      • @Mat:很明显,这不会发生在这里。在 C++0x 中,程序的不终止实际上使它成为 UB;最好不要这样做
      【解决方案5】:

      这很有趣,在 stackoverflow.com 上谈论堆栈溢出。 ;) 调用栈是有限的(你可以从项目设置中自定义它),但是在某些时候,当你有无限循环调用时,它会被超出并且你的程序终止。

      【讨论】:

        【解决方案6】:

        如果您想通过无限递归避免堆栈溢出,不幸的是,您将不得不深入研究一些程序集以更改堆栈,以便新的激活记录不会不断地推入堆栈,之后某些点会导致溢出。因为您在函数末尾进行递归调用,所以在递归流行的其他语言(即 Lisp、Scheme、Haskell 等)中调用它是一种跟踪调用优化。它通过基本上将尾调用转换为循环来防止堆栈溢出。在 C 语言中会是这样的(注意:我在 x86 上使用带有 gcc 的内联汇编,我将您的参数从 double 更改为 int 以简化汇编。我也从C++ 为了避免函数名的名称混淆。最后,每个语句末尾的“\n\t”不是实际的汇编命令,而是 gcc 中的内联汇编所必需的):

        #include <stdio.h>
        
        void rek(int x)
        {
            printf("Value for x: %d\n", x);
        
            //we now duplicate the equvalent of `rek(x+1);` with tail-call optimization
        
            __asm("movl 8(%ebp), %eax\n\t"   //get the value of x off the stack
                  "incl %eax\n\t"            //add 1 to the value of x
                  "movl 4(%ebp), %ecx\n\t"   //save the return address on the stack
                  "movl (%ebp), %edx\n\t"    //save the caller's activation record base pointer
                  "addl $12, %ebp\n\t"       //erase the activation record
                  "movl %ebp, %esp\n\t"      //reset the stack pointer
                  "pushl %eax\n\t"           //push the new value of x on the stack for function call
                  "pushl %ecx\n\t"           //push the return value back to the caller (i.e., main()) on the stack
                  "movl %edx, %ebp\n\t"      //restore the old value of the caller's stack base pointer
                  "jmp rek\n\t");            //jump to the start of rek()
        }
        
        int main()
        {
            rek(1);
            printf("Finished call\n");  //<== we never get here
        
            return 0;
        }
        

        在 Ubuntu 10.04 上使用 gcc 4.4.3 编译,它在无限循环中几乎“永远”运行,没有堆栈溢出,因为没有尾调用优化,它很快就因分段错误而崩溃。您可以从__asm 部分中的 cmets 看到堆栈激活记录空间是如何被“回收”的,因此每个新调用都不会用完堆栈上的空间。这包括将键值保存在旧的激活记录中(前一个调用者的激活记录基指针和返回地址),然后恢复它们,但参数会更改,以便下次递归调用函数。

        同样,其他语言,主要是函数式语言,执行尾调用优化作为该语言的基本功能。因此,Scheme/Lisp/等中的尾调用递归函数。不会溢出堆栈,因为当新函数调用作为现有函数的最后一条语句时,这种类型的堆栈操作会在后台为您完成。

        【讨论】:

          【解决方案7】:

          你已经定义了无限递归和溢出堆栈,这会杀死你的应用程序。如果您真的想打印所有数字;然后使用循环。

          int main(...)
          {
             double x = 1;
             while (true)
             {
                 std:cout << x << std::endl;
                 x += 1;
             }
           }
          

          【讨论】:

            【解决方案8】:

            每个递归方法都应该实现一个退出条件,否则会导致堆栈溢出,程序将终止。

            在您的情况下,您传递给函数的参数没有条件,因此,它永远运行并最终崩溃。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2019-05-28
              • 1970-01-01
              • 2020-12-26
              • 1970-01-01
              • 2018-04-23
              • 1970-01-01
              • 2020-04-29
              • 2020-07-09
              相关资源
              最近更新 更多