Stapel erlauben es uns, die durch die endliche Anzahl von Registern auferlegten Grenzen elegant zu umgehen.
Stellen Sie sich vor, Sie haben genau 26 globale "Register az" (oder sogar nur die 7-Byte-Register des 8080-Chips). Jede Funktion, die Sie in diese App schreiben, teilt diese flache Liste.
Ein naiver Start wäre, die ersten paar Register der ersten Funktion zuzuweisen, und zu wissen, dass es nur 3 dauerte, mit "d" für die zweite Funktion zu beginnen ... Sie sind schnell erschöpft.
Stattdessen , wenn Sie eine metaphorische Band haben, wie die Turing - Maschine, können Sie jede Funktion einen „Aufruf einer anderen Funktion“ starten , indem alle Variablen Spar es mit und nach vorn () , um das Band, und dann kann der Angerufene Funktion wursteln mit so vielen registriert sich wie es will. Wenn der Anruf beendet ist, gibt er die Kontrolle an die übergeordnete Funktion zurück, die weiß, wo die Ausgabe des Anrufs nach Bedarf abzufangen ist, und spielt das Band dann rückwärts ab, um seinen Zustand wiederherzustellen.
Ihr grundlegender Aufrufrahmen ist genau das und wird durch standardisierte Maschinencodesequenzen erstellt und gelöscht, die der Compiler um die Übergänge von einer Funktion zur anderen erstellt. (Es ist lange her, dass ich mich an meine C-Stack-Frames erinnern musste, aber Sie können sich über die verschiedenen Möglichkeiten informieren , welche Aufgaben bei X86_calling_conventions fallen gelassen werden .)
(Rekursion ist fantastisch, aber wenn Sie jemals Register ohne Stapel jonglieren müssten, würden Sie Stapel wirklich schätzen.)
Ich nehme an, dass der erhöhte Festplattenspeicher und RAM, die zum Speichern des Programms bzw. zur Unterstützung seiner Kompilierung erforderlich sind, der Grund ist, warum wir Aufrufstapel verwenden. Ist das korrekt?
Heutzutage können wir zwar mehr inlineen ("mehr Geschwindigkeit" ist immer gut; "weniger KB Assembly" bedeutet in einer Welt mit Videostreams sehr wenig). Die Haupteinschränkung liegt in der Fähigkeit des Compilers, bestimmte Codemustertypen zu reduzieren.
Zum Beispiel polymorphe Objekte - wenn Sie den einzigen Objekttyp, den Sie erhalten, nicht kennen, können Sie ihn nicht abflachen. Sie müssen sich die Vtable der Features des Objekts ansehen und diesen Zeiger durchlaufen ... was zur Laufzeit trivial ist und zur Kompilierungszeit nicht möglich ist.
Eine moderne Toolchain kann gerne eine polymorph definierte Funktion einbinden, wenn die Anzahl der Aufrufer abgeflacht ist, um genau zu wissen , welche Variante von obj ist:
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 den obigen Fällen kann der Compiler festlegen, dass das statische Inlining von InlineMe über die in act () enthaltenen Elemente beibehalten werden soll oder dass zur Laufzeit keine vtables berührt werden müssen.
Aber jede Unsicherheit in dem, was Geschmack des Objekts verlassen wird es als ein Anruf auf eine diskrete Funktion, auch wenn einige andere Anrufungen der gleichen Funktion sind inlined.