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++
. With optimization at defaults, it causes a stack overflow, but at optimization level -O3
it just goes into an infinite loop.
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.