2014-11-12 5 views
5

In C, wie viele von Ihnen wissen, befinden sich alle lokalen Variablen im Stapel. Da der Stack eine First-in-Last-Out-Datenstruktur darstellt, können Sie nur auf das zugreifen, was zuletzt auf ihn übertragen wurde. So den folgenden Code angegeben:Was ist die Idee hinter einem Stack für lokale Variablen?

int k = 5; 
int j = 3; 
short int i; 

if (k > j) i = 1; 

Offensichtlich ist dies nutzlos Code, der keine wirkliche Bedeutung hat, aber ich versuche, meinen Kopf um etwas zu wickeln.

Für die kurze int i Deklaration nehme ich an, dass 2 Bytes auf dem Stack zugewiesen werden. Für int k und int j für beide 4 Bytes erhalten mit den Werten zugeordnet 5 und 3. So würde der Stapel aus, als

---------- <- stack pointer 
int i 
---------- 
int k = 5 
---------- 
int j = 3 
---------- 

so folgt für die if-Anweisung Sie würde den int Pop habe ich das bekommen Bedingungen k und j, und wenn ja, wohin int gehe ich? Dies alles scheint sehr zeitaufwendig und langwierig zu sein, wenn C so lokale Variablen verwendet.

Also ist das tatsächlich, wie C es tut, oder bin ich mucking alles auf?

+0

tatsächlich ist Ihr Layout der Elemente auf dem Stapel falsch, da ich ein kurzer, nicht ein int bin. und ein Stapel wächst im Speicher nach unten, nicht nach oben, und lokale Variablen werden in umgekehrter Reihenfolge auf den Stapel gelegt. Unabhängig davon wird jeder Wert auf dem lokalen Stapel NICHT gepoppt, sondern ein Offset vom Stapelzeiger wird verwendet, so dass Bezug auf 'k' in "Wort lesen (irgendein Register) von sp [(Offset zu k)" ähnlich für das 'j 'Variable. und 'i' wird gesetzt als "schreibe Halbwort sp [Offset zu i] von (untere Hälfte des Registers, das das Ergebnis von k + j enthält) Im Allgemeinen kann man sich den Stapel als ein langes Array mit Partitionen vorstellen – user3629249

Antwort

4

Sie meckern es immer so leicht.

Ja, lokale Variablen (auto) werden normalerweise auf einem Stapel gespeichert. Sie werden jedoch beim Lesen nicht vom Stapel genommen. Sie werden durch einen Versatz vom Stapelzeiger referenziert.

Nehmen Sie den folgenden Code ein:

x = y + z; 

wo jeder x, y und z werden auf dem Stapel reserviert.Wenn die Compiler den äquivalenten Maschinencode erzeugt, um es zu jeder Variablen durch einen von einem gegebenen Register Offset bezieht, mag Art:

mov -8(%ebp), %eax 
add -12(%ebp), %eax 
mov %eax, -4(%ebp) 

Auf x86-Architekturen sind die %ebpRahmenzeiger; Der Stack ist in Frames aufgeteilt, wobei jeder Frame Funktionsparameter (falls vorhanden), die Rücksprungadresse (dh die Adresse des Befehls, der auf den Funktionsaufruf folgt) und lokale Variablen (falls vorhanden) enthält. Auf den Systemen, die ich kenne, wächst der Stack "nach unten" in Richtung 0, und lokale Variablen werden "unterhalb" des Frame-Pointers (untere Adressen) gespeichert, daher der negative Offset. Der obige Code geht davon aus, dass x-4(%ebp) ist, y ist -8(%ebp) und z ist -12(%ebp).

Alles wird vom Stapel ausgegeben wenn die Funktion zurück, aber nicht vorher.

EDIT

Bitte beachten Sie, dass keine dies durch die Sprache C Definition vorgeschrieben ist. Die Sprache erfordert nicht die Verwendung eines Laufzeit-Stacks überhaupt (obwohl ein Compiler wäre eine Hündin ohne eine zu implementieren). Sie definiert lediglich die Lebensdauer von Variablen vom Ende ihrer Deklaration bis zum Ende ihres umschließenden Gültigkeitsbereichs. Ein Stapel macht das einfach, aber es ist nicht erforderlich.


1. Nun, die Stapel- und Rahmenzeiger werden auf neue Werte gesetzt; Die Daten bleiben dort, wo sie waren, aber dieser Speicher steht nun für etwas anderes zur Verfügung.

7

Der Stapel ist kein Stapel. Es ist immer noch Random Access Speicher, was bedeutet, dass Sie auf jeden Ort in konstanter Zeit zugreifen können. Der einzige Zweck der Stack-Disziplin besteht darin, jedem Funktionsaufruf einen eigenen, privaten Speicherbereich zu geben, von dem die Funktion sicher sein kann, dass er von niemandem verwendet wird.

+0

In der Tat, wenn Sie schauen Bei der Disassemblierung einer C-Funktion werden Sie keine Push- und Popup-Befehle für lokale Variablen finden. Sie werden einen konstanten Offset vom Basisstapelzeiger finden - die Variablen befinden sich an einem sehr vorhersagbaren Ort und befinden sich zusätzlich im Arbeitsspeicher. Der Befehl zum Laden von "k" im Beispiel des OP würde zum Beispiel aussehen wie "mov eax, [ebp-4]" - der Stapelzeiger selbst, der innerhalb einer Funktion variabel ist, wird überhaupt nicht einmal für Einheimische verwendet. –

+0

Es scheint, dass mehrere Quellen mit Ihnen nicht übereinstimmen, hier ist einer von ihnen: http://gribbylab.org/CBootcamp/7_Memory_Stack_vs_Heap.html Hinweis "er Stack ist eine" FILO "(First in, Last out) Datenstruktur, das ist von der CPU ziemlich genau verwaltet und optimiert. " Ich sollte darauf hinweisen, dass ich nicht sage, dass es den Hardware-Stack-Pointer verwendet, aber es ist tatsächlich ein FILO-Stack – Anthony

+1

Es könnte helfen, den generierten Code zu betrachten run 'gcc -c foo.c' dann' objdump -d foo.o '(optional Hinzufügen von '-M Intel' zu objdump, wenn Sie wie ich sind und hassen die AT & T-Syntax) und Sie können sehen, was tatsächlich generiert wird. Obwohl ich denke, das hilft nicht, wenn man die Assemblersprache nicht lesen kann ... aber kurz gesagt, die Aufrufanweisung drückt eine Rückkehradresse zum Stapel, dann benutzt die Funktion den Speicher neben dem für Einheimische Die Rückkehr bringt das Zeug wieder zum Vorschein. Es ist also eher ein Array-Stack als ein reiner Stack. –

0

Der Anfang des Stapels, auf einem Intel-Prozessor und viele andere, wird durch eine in einem CPU-Register gespeicherte Adresse referenziert, nennen Sie es SP, die in den Basiszeiger kopiert wird, rufen Sie BP; Viele Maschinenbefehle erlauben einen Adressausdruck, der aus dem aktuellen BP kombiniert mit einem Byte-Offset besteht. In Ihrem Beispiel wäre ich also Offset 0, j wäre Offset -2 und k wäre Offset -6.

die würde einfach zu einem Vergleich der Inhalte der Adressen -6 (BP) und -4 (BP) auflösen. die tatsächlichen Offset-Werte können sich von Implementierung zu Implementierung unterscheiden; aber das ist die allgemeine Idee ...

4

Der Aufrufstack ist ein Stapel, aber es wird nicht verwendet, wie Sie sich vorstellen. Jedes Mal, wenn ein Aufruf von einer Funktion ausgeführt wird, wird die Rücksprungadresse (der Programmzähler) zusammen mit den lokalen Variablen auf den Stapel geschoben. Wenn jede Funktion zurückkehrt, wird der Stapel von einem sogenannten "Stapelrahmen" aufgeklappt, der die Variablen enthält. Innerhalb jeder Funktion wird der Speicher als Direktzugriff behandelt. Der Compiler, der den Code generiert hat, der die lokalen Variablen auf dem Stapel geordnet hat, weiß genau, wie weit er vom Stapelrahmenzeiger entfernt ist, und muss daher die einzelnen lokalen Variablen nicht drücken und auffüllen.