Grundlegendes zum Stack-Frame eines Funktionsaufrufs in C / C ++?

19

Ich versuche zu verstehen, wie Stapelrahmen erstellt werden und welche Variablen (Parameter) in welcher Reihenfolge zum Stapeln verschoben werden. Einige Suchergebnisse haben gezeigt, dass der C / C ++ - Compiler basierend auf Operationen entscheidet, die innerhalb einer Funktion ausgeführt werden. Wenn die Funktion beispielsweise einen übergebenen int-Wert nur um 1 erhöhen und zurückgeben sollte (ähnlich dem ++ - Operator), würden alle Parameter der Funktion und der lokalen Variablen in Register eingetragen.

Ich frage mich, welche Register für die Rückgabe oder Übergabe von Wertparametern verwendet werden. Wie werden Referenzen zurückgegeben? Wie wählt der Compiler zwischen eax, ebx, ecx und edx?

Was muss ich wissen, um zu verstehen, wie Register, Stapel- und Heap-Referenzen während Funktionsaufrufen verwendet, erstellt und zerstört werden?

Gana
quelle
Das ist ziemlich schwer zu lesen (Textwand). Hätten Sie etwas dagegen bearbeiten ing Ihre Post in eine bessere Form?
gnat
1
Diese Frage scheint mir ziemlich weit zu gehen. Wird das nicht auch sehr plattformspezifisch sein?
Kazark
Die Frage wurde auch auf SO gestellt: stackoverflow.com/questions/16088040/…
Wayne Conrad
Siehe auch meine Antwort auf SO
Basile Starynkevitch

Antworten:

11

Zusätzlich zu dem, was Dirk sagte, besteht eine wichtige Verwendung von Stapelrahmen darin, vorherige Werte von Registern zu speichern, damit sie nach einem Funktionsaufruf wiederhergestellt werden können. Selbst auf Prozessoren, in denen Register zum Übergeben von Parametern, Zurückgeben eines Werts und Speichern der Rückgabeadresse verwendet werden, werden die Werte dieser Register vor einem Funktionsaufruf auf dem Stapel gespeichert, sodass sie nach dem Aufruf wiederhergestellt werden können. Auf diese Weise kann eine Funktion eine andere aufrufen, ohne ihre eigenen Parameter zu überschreiben oder ihre eigene Rücksprungadresse zu vergessen.

Das Aufrufen einer Funktion B von Funktion A auf einem typischen "generischen" System kann daher die folgenden Schritte umfassen:

  • Funktion A:
    • Drücken Sie das Leerzeichen für den Rückgabewert
    • Parameter drücken
    • Schieben Sie die Absenderadresse
  • springe zur Funktion B
  • Funktion B:
    • Schieben Sie die Adresse des vorherigen Stapelrahmens
    • Push-Werte von Registern, die diese Funktion verwendet (damit sie wiederhergestellt werden können)
    • Push-Platz für lokale Variablen
    • die notwendige Berechnung durchführen
    • Stellen Sie die Register wieder her
    • Stellt den vorherigen Stapelrahmen wieder her
    • Speichern Sie das Funktionsergebnis
    • Zur Absenderadresse springen
  • Funktion A:
    • Pop die Parameter
    • Pop den Rückgabewert

Dies ist keineswegs die einzige Möglichkeit, wie Funktionsaufrufe funktionieren können (und ich habe möglicherweise ein oder zwei Schritte in der falschen Reihenfolge), aber es sollte Ihnen eine Vorstellung davon geben, wie der Stapel verwendet wird, damit der Prozessor verschachtelte Funktionsaufrufe verarbeitet.

Caleb
quelle
Was bedeutet hier genau "schieben"? Ich habe keine Ahnung, was ich davon halten soll.
Tomáš Zato - Wiedereinsetzung von Monica am
2
@ TomášZato pushund popsind die beiden Grundoperationen auf einem Stapel. Ein Stapel ist wie ein Stapel Bücher eine Last-In-First-Out-Struktur. Wenn Sie dies pushtun, legen Sie ein neues Objekt auf den Stapel. wenn Sie popein Objekt von der Oberseite des Stapels nehmen. Es ist nicht erlaubt, Objekte in der Mitte einzufügen oder zu entfernen. Sie können nur auf der Oberseite des Stapels arbeiten. Weitere Informationen zu Stacks im Allgemeinen und zum Programmstack im Besonderen finden Sie auf Wikipedia.
Caleb
11

Dies hängt von der verwendeten Aufrufkonvention ab. Wer die Aufrufkonvention definiert, kann diese Entscheidung nach Belieben treffen.

In der gängigsten Aufrufkonvention für x86 werden Register nicht zum Übergeben von Parametern verwendet. Die Parameter werden beginnend mit dem Parameter ganz rechts auf den Stack übertragen. Der Rückgabewert wird in eax platziert und kann edx verwenden, wenn es zusätzlichen Platz benötigt. Verweise und Zeiger werden in Form einer Adresse in eax zurückgegeben.

Dirk Holsopple
quelle
5

Wenn Sie Stack sehr gut verstehen, werden Sie verstehen, wie Speicher in einem Programm funktioniert, und wenn Sie verstehen, wie Speicher in einem Programm funktioniert, werden Sie verstehen, wie Funktionen in einem Programm gespeichert werden, und wenn Sie verstehen, wie Funktionen in einem Programm gespeichert werden, werden Sie verstehen, wie rekursive Funktionen funktionieren und wenn Sie verstehen, wie die rekursive Funktion funktioniert. Sie werden verstehen, wie der Compiler funktioniert. Wenn Sie verstehen, wie der Compiler funktioniert, werden Ihre Gedanken als Compiler arbeiten und Sie werden jedes Programm sehr einfach debuggen

Lassen Sie mich erklären, wie der Stack funktioniert:

Zuerst müssen Sie wissen, wie Funktionen im Stack gespeichert werden:

Werte für die dynamische Speicherzuweisung im Heapspeicher. Stapeln Sie die automatischen Zuordnungs- und Löschwerte.

Bildbeschreibung hier eingeben

Verstehen wir anhand eines Beispiels:

def hello(x):
    if x==1:
        return "op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(4)

Verstehe nun Teile dieses Programms:

Bildbeschreibung hier eingeben

Nun wollen wir sehen, was Stapel ist und was Stapelteile sind:

Bildbeschreibung hier eingeben

Aufteilung des Stapels:

Denken Sie daran, dass eine Funktion, wenn sie "return" erhält, unabhängig davon, ob sie alle ihre lokalen Variablen geladen hat, oder alles, was sie sofort vom Stapel zurückgibt, in den Stapelrahmen zurückkehrt. Wenn eine rekursive Funktion eine Grundbedingung erhält und die Rückgabe nach der Grundbedingung erfolgt, wartet die Grundbedingung nicht darauf, lokale Variablen zu laden, die sich im Programmteil "else" befinden, sondern gibt sofort den aktuellen Frame vom Stapel und jetzt einen Frame zurück Das nächste Bild wird im Aktivierungsdatensatz angezeigt. Sehen Sie dies in der Praxis:

Bildbeschreibung hier eingeben

Freigabe des Blocks:

Wenn also eine Funktion eine return-Anweisung gefunden hat, wird der aktuelle Frame vom Stapel gelöscht.

Wenn Sie vom Stapel zurückkehren, wird der Wert in umgekehrter Reihenfolge zurückgegeben, in der er im Stapel zugewiesen wurde.

Bildbeschreibung hier eingeben

Dies ist eine sehr kurze Beschreibung. Wenn Sie mehr über Stapel und doppelte Rekursion erfahren möchten, lesen Sie zwei Beiträge in diesem Blog:

Mehr über das Stapeln Schritt für Schritt

Mehr zu Doppelrekursion Schritt für Schritt mit Stack

user5904928
quelle
3

Was Sie suchen, heißt Application Binary Interface - ABI.

Für jeden Compiler gibt es eine Spezifikation, die die ABI beschreibt.

Jede Plattform wird normalerweise eine ABI angeben, um die Interoperabilität zwischen Compilern zu unterstützen. Beispielsweise werden in x86- Aufrufkonventionen die typischen Aufrufkonventionen für x86 und x86-64 beschrieben. Ich würde jedoch ein offizielleres Dokument als Wikipedia erwarten.

Bill Door
quelle