C tail call optimization

Viewed 19310

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?

8 Answers

For those who like proof by construction, here is godbolt doing a nice tail call optimisation and inline: https://godbolt.org/z/DMleUN

However, if you crank the optimization to -O3 (or no doubt if you wait a few years or use a different compiler), the optimisation totally removes the loop/recursion.

Here is an example that optimizes down to a single instruction even with -O2: https://godbolt.org/z/CNzWex

It is common for compilers to recognize situations where a function won't need to do anything after calling another, and replace that call with a jump. Many cases where that can be done safely are easy to recognize, and such cases qualify as "safe low-hanging fruit". Even on compilers that can perform such optimization, however, it may not always be obvious when it should or will be performed. Various factors may make the cost of a tail call greater than that of a normal call, and these factors may not always be predictable. For example, if a function ends with return foo(1,2,3,a,b,c,4,5,6);, it may be practical to copy a, b, and c into registers, clean up the stack and then prepare the arguments for passing, but there may not be enough registers available to handle foo(a,b,c,d,e,f,g,h,i); likewise.

If a language had a special "tail call" syntax that required that compilers given that make a tail call if at all possible, and refuse compilation otherwise, code could safely assume such functions could be nested arbitrarily deep. When using ordinary call syntax, however, there's no general way to know whether a compiler would be able to perform a tail call more cheaply than an "ordinary" one.

Related