[Edit: just noticed that although your title says “be inline”, your actual question says “do functions inline”. These two actually have nothing to do with each other, they just have vaguely similar names. In modern compilers, the main inline effect is what was originally on C99 (I think), just a necessary detail to do the built-in work in general: to allow several character definitions with external connection. This is because modern compilers do not pay much attention to the opinion of the programmer about whether the function should be built-in. However, they pay, so the confusion of concepts persists. I answered the question in the header, which is the decision that the compiler makes, and not the question in the body, which is the decision of the programmer.]
An investment is not necessarily an all-or-nothing transaction. One strategy that compilers use to decide whether it is built-in is to continue to embed function calls while the resulting code is "too large." "Big" is defined by some reliable reasonable heuristic.
So, consider the following recursive function (which is not intentionally tail-recursive):
int triangle(int n) { if (n == 1) return 1; return n + triangle(n-1); }
If it is called like this:
int t100() { return triangle(100); }
Then there is no fundamental reason, in principle, that the usual rules used by the compiler for embedding should not lead to the following:
int t100() { // inline call to triangle(100) int result; if (100 == 1) { result = 1; } else { // inline call to triangle(99) int t99; if (100 - 1 == 1) { t99 = 1; } else { // inline call to triangle(98) int t98; if (100 - 1 - 1 == 1) { t98 = 1; } else { // oops, "too big", no more inlining t98 = triangle(100 - 1 - 1 - 1) + 98; } t99 = t98 + 99; } result = t99 + 100; } return result; }
Obviously, the optimizer will have a field day with this, so it is much “smaller” than it looks:
int t100() { return triangle(97) + 297; }
The code in triangle itself can be “deployed” in several steps to several insertion levels, in the same way, except that it does not have the advantages of constants:
int triangle(int n) { if (n == 1) return 1; if (n == 2) return 3; if (n == 3) return 6; return triangle(n-3) + 3*n - 3; }
I doubt that compilers really do this, although I don't think I ever noticed this [Edit: MSVC, if you say that, thanks peterchen].
There is an obvious potential benefit in maintaining overhead, but by contrast, people do not expect recursive functions to be included in the system, and there is no particular guarantee that regular heuristic sketches will work well with recursive functions (where there are two different locations, call site and recursive call that can be embedded, with different advantages in each case). In addition, during compilation, it is difficult to evaluate how deep the recursion, and the built-in heuristics, would probably like to consider the depth of the call for decision making. Perhaps the compiler is simply not worried.
Functional language compilers are usually much more aggressive than recursion than C or C ++ compilers. The corresponding trade-off is that so many functions written in functional languages ​​are recursive that performance can be hopeless if the compiler cannot optimize tail recursion. Therefore, Lisp programmers usually rely on good optimization of recursive functions, while C and C ++ programmers usually do not.