c ++递归退出没有明显的原因

时间:2011-05-18 14:01:59

标签: c++ gcc ubuntu recursion infinite

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

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

在我的电脑上,此功能在约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);
}

8 个答案:

答案 0 :(得分:5)

你很有可能在堆栈中溢出,此时你的程序将被立即杀死。堆栈的深度将始终限制您可以递归的数量,如果您达到该限制,则意味着您的算法需要更改。

答案 1 :(得分:3)

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

中所述

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

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

我表示你将优化标志设置为O2或O3。

答案 2 :(得分:2)

由于您没有提供退出条件,导致堆栈溢出(堆栈空间不足)。

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

答案 3 :(得分:1)

你期待这个永远工作吗?

不会。在某些时候,你将耗尽堆栈。

答案 4 :(得分:1)

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

答案 5 :(得分:1)

如果你想避免带有无限递归的堆栈溢出,你可能不得不深入研究一些程序集以便更改堆栈,这样新的激活记录就不会一直被推到堆栈上了,某些点会导致溢出。因为你在函数的末尾进行递归调用,所以在其他语言中调用它,其中递归是流行的(即Lisp,Scheme,Haskell等)跟踪调用优化。它通过基本上将尾调用转换为循环来防止堆栈溢出。在C中它会是这样的(注意:我在x86上使用gcc的内联汇编,并且我将参数从int更改为double以简化汇编。我也是从C ++改为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部分的注释中看到堆栈激活记录空间是如何“回收”的,这样每个新调用都不会占用堆栈上的空间。这涉及将键值保存在旧的激活记录(前一个调用者的激活记录基指针和返回地址)中,并恢复它们,但是为下一次递归调用函数更改了参数。

同样,其他语言(主要是函数语言)执行尾调用优化作为语言的基本功能。所以在Scheme / Lisp / etc中有一个尾调用递归函数。不会溢出堆栈,因为当新函数调用作为现有函数的最后一个语句时,这种类型的堆栈操作是在你内部完成的。

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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