Call Stack beginnt unten oder oben?

10

Ein Stapel ist etwas, das sich von unten nach oben stapelt.

Daher fügt ein Aufrufstapel neue Elemente zum Stapel hinzu, wenn Funktionen aufgerufen werden, wobei Elemente aus dem Stapel entfernt werden, wenn jede Funktion endet, bis der Stapel leer ist und das Programm dann endet.

Wenn das oben Gesagte richtig ist, warum beziehen sich die Leute auf die Steuerung, die den Aufrufstapel "nach oben" bewegt? Sicherlich bewegt sich die Kontrolle den Aufrufstapel hinunter , bis er den Boden erreicht.

CJ7
quelle
Wenn eine Funktion aufgerufen wird, wird ein Element oben im Stapel hinzugefügt und das Steuerelement an diese Funktion übergeben. Daher bewegt sich das Steuerelement vom zugrunde liegenden Element im Stapel zum obersten Element - nach oben.
Treecoder
1
@greengit: Der Ausdruck "up the call stack" wird mit Ausnahmen verwendet, bei denen sich die Steuerung tatsächlich in die entgegengesetzte Richtung bewegt.
Michael Borgwardt
@ MichaelBorgwardt: Du bist richtig.
Treecoder
1
@MichaelBorgwardt: Ich habe den Ausdruck „Fehler bewegen gesehen auf den Call - Stack“. Das ist doch falsch.
CJ7

Antworten:

8

Es gibt zwei mögliche Gründe für diese Verwendung:

  • Im Zusammenhang mit Ausnahmen wechselt das Steuerelement zur aufrufenden Funktion / Methode, und diese Aufrufhierarchie wird normalerweise mit der Hauptmethode oben und Methodenaufrufen visualisiert, die eine Hierarchie nach unten bilden, wobei die Abstraktionsebene abnimmt. In dieser Hierarchie wird eine Ausnahme nach oben verschoben.

  • Der eigentliche Programmstapel in einer normalen x86-Anwendung ist invertiert, dh er wächst nach unten. Die Anweisungen für den PUSH / PUSHW / PUSHD-Maschinencode verringern den Stapelzeiger. Andere Architekturen teilen dieses Modell möglicherweise.

Michael Borgwardt
quelle
Widerspricht die Top-Down-Idee nicht dem alltäglichen Konzept, dass ein "Stapel" ein Stapel von Gegenständen ist, der unten beginnt?
CJ7
@CraigJ: Dies gilt auch für die Tatsache, dass die Bits jedes Bytes des Stapelinhalts physisch in separaten Chips gespeichert werden. Wen interessiert das?
Michael Borgwardt
1

Es hängt alles von der Definition der Wörter ab; Was genau meinen Sie in diesem Zusammenhang mit den Worten "oben" und "unten" und auch mit der Implementierung des Betriebssystems oder der Computerarchitektur?

Ich erinnere mich an Folgendes vor langer Zeit, als ich auf dem Commodore 64 programmierte. Der Speicher zwischen der Adresse $ 0800 (2048) und $ 9FFF (40959) war für BASIC-Programme reserviert. Der Code Ihres BASIC-Programms wurde ab der unteren Adresse gespeichert ($ 0800, von dort aufwärts wachsend). Der Stapel zum Speichern von Variablen und Rücksprungadressen von Unterroutinen begann am oberen Rand ($ 9FFF) dieses Bereichs und wuchs in Richtung niedrigerer Adressen. In diesem Zusammenhang war es logisch, den Stapel als nach unten wachsend zu betrachten, und wenn Sie von einer Unterroutine zurückkehren, wurde der Stapelrahmen der Unterroutine durch Inkrementieren des Stapelzeigers verworfen, so dass Sie sagen konnten, dass Sie "den Stapel nach oben bewegen", wenn Rückkehr von einem Unterprogramm.

Ich weiß nicht, wie es auf modernen Versionen von beispielsweise Windows- oder Intel x86-Prozessoren funktioniert. Vielleicht funktioniert der Stapel umgekehrt, dh er wächst von niedrigeren zu höheren Adressen. Wenn dies der Fall wäre, würden Sie wahrscheinlich die Wörter "oben", "unten" und "oben", "unten" genau umgekehrt verwenden.

Jesper
quelle
0

So rufen Sie eine Funktion wie foo (6, x + 1) auf ...

  1. Bewerten Sie die tatsächlichen Parameterausdrücke, z. B. x + 1, im Kontext des Aufrufers.
  2. Weisen Sie den Einheimischen von foo () Speicher zu, indem Sie einen geeigneten "lokalen Block" des Speichers auf einen zu diesem Zweck bestimmten "Aufrufstapel" zur Laufzeit verschieben. Speichern Sie für Parameter, jedoch keine lokalen Variablen, die Werte aus Schritt (1) im entsprechenden Steckplatz im lokalen Block von foo ().
  3. Speichern Sie die aktuelle Ausführungsadresse des Anrufers (seine "Rücksprungadresse") und wechseln Sie die Ausführung zu foo ().
  4. foo () wird mit seinem lokalen Block ausgeführt, der bequem am Ende des Aufrufstapels verfügbar ist.
  5. Wenn foo () fertig ist, wird es beendet, indem seine Einheimischen vom Stapel genommen werden und unter Verwendung der zuvor gespeicherten Rücksprungadresse zum Aufrufer "zurückkehren". Jetzt befinden sich die Einheimischen des Anrufers am Ende des Stapels und können die Ausführung fortsetzen.

Referenz:

http://cslibrary.stanford.edu/102/PointersAndMemory.pdf (S. 15)

CodeART
quelle
Beachten Sie, dass dies sehr spezifisch für die aufrufende Konvention ist. Es gibt Aufrufkonventionen, mit denen der Anrufer bereinigen kann, die meisten verwenden zumindest einige Register usw.
0

Wenn Sie sich einen Stapel als Bottom-up-Objekt vorstellen, wie einen Zylinder mit Tennisbällen unter normaler Gravitationsrealität, bewegt die Steuerung den Stapel nach oben, wenn Funktionen aufgerufen werden. Wenn die Funktionen abgeschlossen sind, bewegt sich die Steuerung den Stapel hinunter.

Wenn Sie sich einen Stapel als Top-Down-Objekt vorstellen, wie denselben Zylinder mit Tennisbällen, jedoch mit umgekehrter Schwerkraft, bewegt sich die Steuerung beim Aufrufen von Funktionen nach oben und beim Ausführen von Funktionen nach oben.

Dies sind nur Modelle in Ihrem Kopf und im Wesentlichen völlig willkürlich. Sie können es sich als eine Seite-zu-Seite-Sache vorstellen, wenn Sie es vorziehen, aber möglicherweise Probleme haben, mit Menschen zu kommunizieren. Ich persönlich denke, wenn A B aufruft und B C aufruft, ist C der Boden des Stapels (invertierte Gravitationsrealität), und wenn in C eine Ausnahme auftritt, möchten Sie diese Ausnahme "bis" zu A sprudeln lassen. Ich denke, dies kann die sein häufigerer Sprachgebrauch, weil das Gefühl ist, dass C tief unten ist und A die Spitze ist. Die erste Funktion ist für mich intuitiver und die Funktionen werden mit jedem Aufruf tiefer.

Ken Falk
quelle