简体   繁体   English

在这种情况下,无限递归调用应该引发堆栈溢出?

[英]Infinite recursive calls should raise a stack overflow in this case?

I've recently thought about this case of stack overflow: 我最近想过这种堆栈溢出的情况:

int f()  
{  
    return f();  
}  

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

which definitely crashes the program. 这肯定会导致程序崩溃。 But my question is why is this not the same as an infinite loop? 但我的问题是为什么这与无限循环不一样? The compiler in the case of a recursive call at the return line could realize there's no need to keep any information of the calling function since the returning point to the called function is the same than that of the caller. 在返回行进行递归调用的情况下,编译器可以意识到不需要保留调用函数的任何信息,因为被调用函数的返回点与调用者的返回点相同。 Now, in this case I agree the compiler needs to keep the information of the functions making the calls in the stack: 现在,在这种情况下,我同意编译器需要保留在堆栈中进行调用的函数的信息:

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

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

For sure I'm missing something, I'd appreciate if someone explains it to me. 我肯定错过了一些东西,如果有人向我解释,我会很感激。 Regards 问候

It turns out that in some compilers, with the right optimization flags, this actually won't cause a stack overflow! 事实证明,在一些编译器中,使用正确的优化标志,这实际上不会导致堆栈溢出! In fact, I tried compiling your program in g++ . 事实上,我尝试用g++编译你的程序。 With optimization at defaults, it causes a stack overflow, but at optimization level -O3 it just goes into an infinite loop. 在默认情况下进行优化时,会导致堆栈溢出,但在优化级别-O3它会进入无限循环。

The reason that there's a difference between infinite recursion and infinite loops has to do with how the compiler, by default, implements these constructs. 无限递归和无限循环之间存在差异的原因与默认情况下编译器如何实现这些结构有关。 Loops historically have been implemented using branching instructions that tell the processor to pick up execution at a different part of the program. 历史上已经使用分支指令实现循环,该分支指令告诉处理器在程序的不同部分获取执行。 All these instructions do is jump the program counter someplace else, which just modifies the contents of a register. 所有这些指令都是在其他地方跳转程序计数器,这只是修改寄存器的内容。 Function calls, on the other hand, are implemented by adding a new activation record to the stack to encode the arguments, return address, etc. so that when the function returns, it knows where to return to. 另一方面,函数调用是通过向堆栈添加新的激活记录来实现的,以对参数,返回地址等进行编码,以便在函数返回时知道返回的位置。

That said, this isn't "the way" that function calls or branches have to be implemented. 也就是说,这不是必须实现函数调用或分支的“方式”。 You could, in theory, implement loops by using function calls and returns, though no compiler does so. 理论上,你可以通过使用函数调用和返回来实现循环,尽管没有编译器这样做。 Similarly, with functions that are tail-recursive (as is your example), compilers are often smart enough to elide all of the stack manipulations and to convert it to a naive branch instruction to avoid the overhead of stack setup and teardown. 类似地,对于尾递归的函数(如您的示例所示),编译器通常足够聪明以消除所有堆栈操作并将其转换为简单的分支指令以避免堆栈设置和拆除的开销。

In short, the reason you get different behavior is based on how the compiler decides to implement the code. 简而言之,您获得不同行为的原因取决于编译器如何决定实现代码。 If it's smart enough to see that it doesn't need to do an expensive function call setup, then it will convert the call into a simple loop instruction and loop forever. 如果足够聪明地看到它不需要进行昂贵的函数调用设置,那么它会将调用转换为简单的循环指令并永远循环。 If it doesn't detect this, then it will fall back on the naive function call mechanism. 如果它没有检测到这个,那么它将回退到天真的函数调用机制。

You aren't missing anything. 你没有遗漏任何东西。 Compile the program using gcc -O2 and there is no stack overflow. 使用gcc -O2编译程序,没有堆栈溢出。

It still needs to push the program counter on the stack each time a new function is called. 每次调用新函数时,它仍然需要在堆栈上推送程序计数器。 That is one of the reason why the stack grows at each function invocation. 这是堆栈在每次函数调用时增长的原因之一。

我的猜测是C编译器编写者可能认为没有必要优化对空函数的无限递归调用的输出......

C is not required to eliminate tail calls. C不需要消除尾调用。 (Scheme is required to perform TCO .) (计划需要执行TCO 。)

I think that it really is a question of the compiler then and how optimized it is. 我认为这真的是编译器的问题,以及它是如何优化的。 I would imagine that in both cases, a while loop and this endlessly recursive call, the machine instructions written are as explicitly as possible converting your wishes into instructions. 我可以想象,在这两种情况下,while循环和这种无休止的递归调用,所编写的机器指令尽可能明确地将您的愿望转换为指令。 As you know, for the while loop, the you are just jmp'ing back to the specific location and continuing from there. 如你所知,对于while循环,你只是jmp'ing回到特定位置并继续从那里。 with the recursive call, you are pushing on a new value on the stack, so, per your question, I guess its really a question of how smart your compiler is ... 使用递归调用,你正在推动堆栈上的新值,所以,根据你的问题,我想这真的是你的编译器有多聪明的问题......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM