Stacks allow us to elegantly bypass the limits imposed by the finite number of registers.
Imagine having exactly 26 globals "registers a-z" (or even having only the 7 byte-sized registers of the 8080 chip)
And every function you write in this app shares this flat list.
A naive start would be to allocate the first few registers to the first function, and knowing that it took only 3, start with "d" for the second function...
You run out quickly.
Instead, if you have a metaphorical tape, like the turing machine, you could have each function start a "call another function" by saving all the variables it's using and forward() the tape, and then the callee function can muddle with as many registers as it wants. When the callee is finished, it returns control to the parent function, who knows where to snag the callee's output as needed, and then plays the tape backwards to restore its state.
Your basic call frame is just that, and are created and dropped by standardized machine code sequences the compiler puts in around the transitions from one function to another. (It's been a long time since I had to remember my C stack frames, but you can read up on the various ways the duties of who drops what at X86_calling_conventions.)
(recursion is awesome, but if you'd ever had to juggle registers without a stack, then you'd really appreciate stacks.)
I suppose the increased hard disk space and RAM needed to store the program and support its compilation (respectively) is the reason why we use call stacks. Is that correct?
While we can inline more these days, ("more speed" is always good; "fewer kb of assembly" means very little in a world of video streams)
The main limitation is in the compiler's ability to flatten across certain types of code patterns.
For example, polymorphic objects -- if you don't know the one and only type of object you'll be handed, you can't flatten; you have to look at the object's vtable of features and call through that pointer... trivial to do at runtime, impossible to inline at compile time.
A modern toolchain can happily inline a polymorphically-defined function when it has flattened enough of the caller(s) to know exactly which flavor of obj is:
class Base {
public: void act() = 0;
};
class Child1: public Base {
public: void act() {};
};
void ActOn(Base* something) {
something->act();
}
void InlineMe() {
Child1 thingamabob;
ActOn(&thingamabob);
}
in the above, the compiler can choose to keep right on statically inlining, from InlineMe through whatever's inside act(), nor a need to touch any vtables at runtime.
But any uncertainty in what flavor of object will leave it as a call to a discrete function, even if some other invocations of the same function are inlined.
window[prompt("Enter function name","")]()
function(a)b { if(b>0) return a(b-1); }
without a stack?b
. But point taken, not all recursive functions can eliminate the recursion, and even when the function can in principle, the compiler might not be smart enough to do so.