views:

141

answers:

4

I often hear people say that C doesn't perform tail call elimination. Even though it's not guaranteed by the standard, isn't it performed in practice by any decent implementation anyhow? Assuming you're only targeting mature, well implemented compilers and don't care about absolute maximum portability to primitive compilers written for obscure platforms, is it reasonable to rely on tail call elimination in C?

Also, what was the rationale for leaving tail call optimization out of the standard?

+6  A: 

Statements like "C doesn't perform tail call elimination" make no sense. As you correctly noted yourself, things like this depend entirely on the implementation. And yes, any decent implementation can easily turn tail-recursion into [an equivalent of] a cycle. Of course, C compilers do not normally give any guarantees about what optimizations will and what optimizations will not happen in each particular piece of code. You have to compile it an see for yourself.

AndreyT
+4  A: 

To answer you last question: The standard should definitely not make any statements about optimization. There may be environments where it is more or less difficult to implement.

Frank
Would it be wrong for the standard to require that while loops run in constant memory? (except for allocations in the while loop) Would it be wrong for the standard to require that tail recursive functions run in constant memory?
Jules
I believe Scheme requires tail-call optimization, but Scheme is a primarily functional language, heavily using recursion as a control structure. C programs tend to be less recursive, and there are iterative structures in constant use.
David Thornley
+1  A: 

The language standard defines how the language behaves, not how compilers are required to be implemented. Optimization isn't mandated because it isn't always wanted. Compilers provide options so that the user can enable optimizations if they so desire them, and can likewise turn them off. Compiler optimization can affect the ability to debug code (it becomes harder to match C to assembly in a line-by-line fashion), so it makes sense to only perform optimization at the user's request.

bta
+2  A: 

I think that tail call optimizations need to be guaranteed only where a lot of recursion is anticipated or required; that is, in languages that encourage or enforce a functional programming style. (With these kinds of languages, you may find that for or while loops are either strongly discouraged, perceived as inelegant, or probably even completely absent from the language, so you would resort to recursion for all these reasons, and probably more.)

The C programming language (IMHO) clearly was not designed with functional programming in mind. There's all kinds of loop constructs that are generally used in favour of recursion: for, do .. while, while. In such a language, it wouldn't make much sense to proscribe tail call optimization in a standard, because it's not strictly required to guarantee working programs.

Contrast this again with a functional programming language that doesn't have while loops: This means you will need recursion; which in turn means that the language must make sure that, with many iterations, stack overflows won't become a problem; thus the official standard for such a language might choose to proscribe tail call optimization.


P.S.: Note a slight flaw in my argument for tail call optimization. Towards the end of, I mention stack overflows. But who says that function calls always require a stack? On some platforms, function calls might be implemented in a totally different way, and stack overflows would never even be a problem. This would be yet another argument against proscribing tail call optimization in a standard. (But don't get me wrong, I can see the merits of such optimizations, even without a stack!)

stakx
However implemented, the general case of a function call will always require saving and restoring some state. Hence, there will always be functions such that too many nested function calls fill up available memory for storing this state. The conventional data structure for this is a stack in a fixed memory block but it can be done differently.Tail call elimination can avoid this saving and restoring for some functions, but a nontrivial function that calls itself twice will need to store state for the second call.
jilles
@jilles: Exactly, tail call optimization should help preserve memory no matter how function calls work. However, one trait of the CPU stack is that it is usually relatively limited in capacity; more so than e.g. heap memory. That's why I mentioned stack overflows, but not a more general out-of-memory condition; I assumed that you would need an almost unbelievable lot of recursion to deplete e.g. 2 GB of memory.
stakx