【问题标题】:Infinite recursive calls should raise a stack overflow in this case?在这种情况下,无限递归调用应该引发堆栈溢出吗?
【发布时间】:2011-07-17 18:22:17
【问题描述】:

我最近想到了这种堆栈溢出的情况:

int f()  
{  
    return f();  
}  

int main(void)  
{  
    f();  
    return 0;  
}

这肯定会使程序崩溃。但我的问题是为什么这与无限循环不同?在返回行递归调用的情况下,编译器可以意识到不需要保留调用函数的任何信息,因为被调用函数的返回点与调用者的返回点相同。 现在,在这种情况下,我同意编译器需要将调用函数的信息保存在堆栈中:

int f()
{
    int x = f();
    return x;
}

int main(void)
{
    f();
    return 0;
}

我确实遗漏了一些东西,如果有人向我解释一下,我将不胜感激。问候

【问题讨论】:

    标签: c recursion stack-overflow


    【解决方案1】:

    事实证明,在某些编译器中,使用正确的优化标志,这实际上不会导致堆栈溢出!事实上,我尝试在g++ 中编译你的程序。默认情况下优化会导致堆栈溢出,但在优化级别-O3 只会进入无限循环。

    无限递归和无限循环之间存在差异的原因与默认情况下编译器如何实现这些构造有关。循环历史上是使用分支指令实现的,这些指令告诉处理器在程序的不同部分继续执行。所有这些指令所做的只是将程序计数器跳转到其他地方,这只是修改了寄存器的内容。而函数调用则是通过在栈中添加一个新的激活记录来实现,对参数、返回地址等进行编码,这样当函数返回时,它就知道返回到哪里。

    也就是说,这不是必须实现函数调用或分支的“方式”。理论上,您可以通过使用函数调用和返回来实现循环,尽管没有编译器这样做。同样,对于尾递归函数(如您的示例),编译器通常足够聪明,可以省略所有堆栈操作并将其转换为简单的分支指令,以避免堆栈设置和拆卸的开销。

    简而言之,您获得不同行为的原因取决于编译器决定如何实现代码。如果它足够聪明,可以看到不需要进行昂贵的函数调用设置,那么它将调用转换为简单的循环指令并永远循环。如果它没有检测到这一点,那么它将退回到朴素的函数调用机制。

    【讨论】:

    • 嘿,谢谢:D。这是一个非常有用且完整的解释,是的,这正是我所关心的:在这些情况下编译器的智能程度。
    • @Nicolas- 很高兴能帮上忙!顺便说一句,如果您喜欢这里的任何答案(即使它不是我的),您应该接受它,以便问题被标记为已解决。
    • 完成 xD。我还在学习浏览这个网站哈哈。我喜欢几乎所有的答案,你的答案是最完整的,不过谢谢^^
    • 不能使用函数调用和返回来实现循环。很容易构造一个严格符合的程序,该程序循环 N 次,以获取相对于 void * 的表示空间大得惊人的 N 的值(通过一个简单的参数限制递归级别的数量),但它不是符合在运行此类程序时因堆栈溢出而崩溃的实现。
    • Nitpick - 应该用 gcc 替换 g++。
    【解决方案2】:

    我认为这确实是编译器的问题,以及它的优化程度。我想在这两种情况下,一个while循环和这个无休止的递归调用,机器 书面说明尽可能明确地将您的愿望转化为说明。如您所知,对于 while 循环,您只是跳回特定位置并从那里继续。通过递归调用,您将在堆栈上推入一个新值,因此,根据您的问题,我想这确实是您的编译器有多聪明的问题...

    【讨论】:

    • 谢谢 :D 是的,我很担心当我尝试的编译器没有优化它时,我有点惊讶,我想这是我的错,因为没有使用适当的参数哈哈。
    【解决方案3】:

    C 不需要消除尾调用。 (执行TCO需要方案。)

    【讨论】:

    • 谢谢 :D 很高兴知道。我会进一步调查
    【解决方案4】:

    我的猜测是,C 编译器编写者可能并不觉得有必要优化对空函数的无限递归调用的输出...

    【讨论】:

    • 大声笑是的,这个例子几乎没用。但我认为在某些特殊情况下优化调用可能会派上用场。
    【解决方案5】:

    每次调用新函数时,它仍然需要将程序计数器压入堆栈。 这就是堆栈在每次函数调用时都会增长的原因之一。

    【讨论】:

    • 谢谢 :D 嗯,我忘了 PC 是的,但我认为没有必要记住这个特定示例中的所有值。
    【解决方案6】:

    你没有错过任何东西。使用gcc -O2编译程序,没有堆栈溢出。

    【讨论】:

    • 嘿,谢谢。哈哈,我想下次我会为我的编译器尝试命令行选项。
    猜你喜欢
    • 2013-03-04
    • 1970-01-01
    • 2017-09-05
    • 1970-01-01
    • 2014-03-19
    • 2013-06-16
    • 1970-01-01
    • 2012-11-27
    • 2020-12-17
    相关资源
    最近更新 更多