Wächst der Stack nach oben oder nach unten?

Lesezeit: 11 Minuten

Ich habe diesen Code in c:

int q = 10;
int s = 5;
int a[3];

printf("Address of a: %d\n",    (int)a);
printf("Address of a[1]: %d\n", (int)&a[1]);
printf("Address of a[2]: %d\n", (int)&a[2]);
printf("Address of q: %d\n",    (int)&q);
printf("Address of s: %d\n",    (int)&s);

Die Ausgabe ist:

Address of a: 2293584
Address of a[1]: 2293588
Address of a[2]: 2293592
Address of q: 2293612
Address of s: 2293608

So sehe ich das aus a zu a[2], Speicheradressen erhöhen sich jeweils um 4 Bytes. Aber von q zu sSpeicheradressen verringern sich um 4 Byte.

Ich frage mich 2 Dinge:

  1. Wächst der Stack nach oben oder unten? (In diesem Fall sieht es für mich nach beidem aus)
  2. Was dazwischen passiert a[2] und q Speicheradressen? Warum gibt es da einen großen Speicherunterschied? (20 Bytes).

Hinweis: Dies ist keine Hausaufgabenfrage. Ich bin gespannt, wie Stack funktioniert. Danke für jede Hilfe.

  • Die Reihenfolge ist willkürlich. Die Lücke besteht wahrscheinlich darin, ein Zwischenergebnis wie &q oder &s zu speichern – schauen Sie sich die Disassemblierung an und überzeugen Sie sich selbst.

    – Tom Leys

    5. November 2009 um 0:03 Uhr

  • Ich stimme zu, lesen Sie den Assembler-Code. Wenn Sie diese Art von Fragen stellen, ist es an der Zeit, sie lesen zu lernen.

    – Per Johansson

    29. August 2011 um 7:08 Uhr

  • Eine einfacher zu beantwortende Assembly-Version: stackoverflow.com/questions/664744/…

    – Ciro Santilli OurBigBook.com

    22. April 2015 um 15:15 Uhr

Das Verhalten des Stacks (aufwachsen oder absteigend) hängt von der Binärschnittstelle der Anwendung (ABI) und davon ab, wie der Aufrufstapel (auch bekannt als Aktivierungsdatensatz) organisiert ist.

Während seiner gesamten Lebensdauer ist ein Programm verpflichtet, mit anderen Programmen wie OS zu kommunizieren. ABI bestimmt, wie ein Programm mit einem anderen Programm kommunizieren kann.

Der Stapel für verschiedene Architekturen kann in beide Richtungen wachsen, aber für eine Architektur ist er konsistent. Bitte prüfen Dies Wiki-Link. Aber das Wachstum des Stacks wird durch die ABI dieser Architektur bestimmt.

Wenn Sie beispielsweise den MIPS ABI nehmen, ist der Aufrufstapel wie folgt definiert.

Betrachten wir, dass die Funktion „fn1“ „fn2“ aufruft. Nun sieht der Stack-Frame, wie er von ‘fn2’ gesehen wird, wie folgt aus:

direction of     |                                 |
  growth of      +---------------------------------+ 
   stack         | Parameters passed by fn1(caller)|
from higher addr.|                                 |
to lower addr.   | Direction of growth is opposite |
      |          |   to direction of stack growth  |
      |          +---------------------------------+ <-- SP on entry to fn2
      |          | Return address from fn2(callee) | 
      V          +---------------------------------+ 
                 | Callee saved registers being    | 
                 |   used in the callee function   | 
                 +---------------------------------+
                 | Local variables of fn2          |
                 |(Direction of growth of frame is |
                 | same as direction of growth of  |
                 |            stack)               |
                 +---------------------------------+ 
                 | Arguments to functions called   |
                 | by fn2                          |
                 +---------------------------------+ <- Current SP after stack 
                                                        frame is allocated

Jetzt können Sie sehen, dass der Stapel nach unten wächst. Wenn also die Variablen dem lokalen Rahmen der Funktion zugewiesen werden, wachsen die Adressen der Variablen tatsächlich nach unten. Der Compiler kann über die Reihenfolge der Variablen für die Speicherzuweisung entscheiden. (In Ihrem Fall kann es entweder ‘q’ oder ‘s’ sein, das zuerst Stapelspeicher zugewiesen wird. Aber im Allgemeinen nimmt der Compiler die Stapelspeicherzuweisung gemäß der Reihenfolge der Deklaration der Variablen vor).

Aber im Fall der Arrays hat die Zuweisung nur einen einzigen Zeiger und der Speicher muss zugewiesen werden und wird tatsächlich von einem einzigen Zeiger angezeigt. Der Speicher muss für ein Array zusammenhängend sein. Obwohl also der Stack nach unten wächst, wächst der Stack für Arrays nach oben.

  • Außerdem, wenn Sie überprüfen möchten, ob der Stapel nach oben oder unten wächst. Deklarieren Sie eine lokale Variable in der Hauptfunktion. Geben Sie die Adresse der Variablen aus. Rufen Sie eine andere Funktion von main auf. Deklarieren Sie eine lokale Variable in der Funktion. Drucken Sie seine Adresse aus. Basierend auf den gedruckten Adressen können wir sagen, dass der Stapel nach oben oder unten wächst.

    – Ganesh Gopalasubramanian

    13. November 2009 um 6:41 Uhr

  • danke Ganesh, ich habe eine kleine Frage: in der Abbildung, die du gezeichnet hast, im dritten Block, meinst du “calleR gespeichertes Register, das in CALLER verwendet wird”, denn wenn f1 f2 aufruft, müssen wir die f1-Adresse speichern (das ist die Rücksendeadr für f2) und f1 (calleR) Register, nicht f2 (callee) Register. Recht?

    – CSawy

    26. April 2013 um 14:26 Uhr

  • @GaneshGopalasubramanian Sie wissen, dass der Compiler die Funktion inline einfügen könnte, oder?

    – xyf

    13. November 2021 um 5:52 Uhr

Benutzeravatar von Crashworks
Absturz

Das sind eigentlich zwei Fragen. Einer ist, in welche Richtung der Stack wächst, wenn eine Funktion eine andere aufruft (wenn ein neuer Rahmen zugewiesen wird), und der andere betrifft die Anordnung von Variablen im Rahmen einer bestimmten Funktion.

Beides wird vom C-Standard nicht spezifiziert, aber die Antworten sind etwas anders:

  • In welche Richtung wächst der Stack, wenn ein neuer Frame zugewiesen wird – wenn die Funktion f() die Funktion g() aufruft, wird fRahmenzeiger von größer oder kleiner als sein gRahmenzeiger von ? Dies kann in beide Richtungen gehen – es hängt vom jeweiligen Compiler und der Architektur ab (siehe “Aufrufkonvention”), aber es ist innerhalb einer gegebenen Plattform immer konsistent (mit ein paar bizarren Ausnahmen, siehe die Kommentare). Abwärts ist häufiger; Dies ist bei x86-, PowerPC-, MIPS-, SPARC-, EE- und den Cell-SPUs der Fall.
  • Wie sind die lokalen Variablen einer Funktion in ihrem Stapelrahmen angeordnet? Dies ist nicht spezifiziert und völlig unvorhersehbar; Der Compiler kann seine lokalen Variablen frei anordnen, um das effizienteste Ergebnis zu erzielen.

  • “es ist innerhalb einer bestimmten Plattform immer konsistent” – nicht garantiert. Ich habe eine Plattform ohne virtuellen Speicher gesehen, bei der der Stapel dynamisch erweitert wurde. Neue Stack-Blöcke wurden tatsächlich mallociert, was bedeutet, dass Sie für eine Weile einen Stack-Block “nach unten” gehen und dann plötzlich “seitwärts” zu einem anderen Block gehen würden. „Seitwärts“ kann eine höhere oder niedrigere Adresse bedeuten, ganz nach dem Glück der Auslosung.

    – Steve Jessop

    5. November 2009 um 2:00 Uhr

  • Für zusätzliche Details zu Punkt 2 – ein Compiler kann möglicherweise entscheiden, dass eine Variable niemals im Speicher sein muss (sie für die Lebensdauer der Variablen in einem Register halten) und/oder ob die Lebensdauer von zwei oder mehr Variablen nicht Da es keine Überlappung gibt, kann der Compiler entscheiden, denselben Speicher für mehr als eine Variable zu verwenden.

    – Michael Burr

    5. November 2009 um 2:01 Uhr

  • Ich denke, S/390 (IBM zSeries) hat eine ABI, bei der Anrufrahmen verknüpft werden, anstatt auf einem Stapel zu wachsen.

    – vergänglich

    5. November 2009 um 3:51 Uhr

  • Korrigieren Sie auf S/390. Ein Anruf ist “BALR”, Verzweigung und Verknüpfung registrieren. Der Rückgabewert wird in ein Register geschrieben und nicht auf einen Stack geschoben. Die Rückgabefunktion ist eine Verzweigung zu den Inhalten dieses Registers. Wenn der Stapel tiefer wird, wird Platz im Heap zugewiesen und sie werden miteinander verkettet. Hier bekommt das MVS-Äquivalent von „/bin/true“ seinen Namen: „IEFBR14“. Die erste Version hatte einen einzigen Befehl: “BR 14”, der zu den Inhalten von Register 14 verzweigte, das die Rücksendeadresse enthielt.

    – Jam

    7. November 2009 um 3:03 Uhr

  • Und einige Compiler auf PIC-Prozessoren führen ganze Programmanalysen durch und weisen den Auto-Variablen jeder Funktion feste Speicherorte zu; der eigentliche Stack ist winzig und von der Software aus nicht zugänglich; es ist nur für Absenderadressen.

    – Jam

    7. November 2009 um 3:08 Uhr

Benutzeravatar von R Samuel Klatchko
R. Samuel Klatschko

Die Richtung, in der Stacks wachsen, ist architekturspezifisch. Mein Verständnis ist jedoch, dass nur sehr wenige Hardwarearchitekturen Stacks haben, die erwachsen werden.

Die Richtung, in der ein Stapel wächst, ist unabhängig vom Layout eines einzelnen Objekts. Während also der Stack kleiner werden kann, werden Arrays dies nicht tun (dh &array[n] wird immer < &array sein[n+1]);

Benutzeravatar von paxdiablo
paxdiablo

Es gibt nichts im Standard, das vorschreibt, wie die Dinge auf dem Stack organisiert sind. Tatsächlich könnten Sie einen konformen Compiler bauen, der Array-Elemente überhaupt nicht in zusammenhängenden Elementen auf dem Stack speichert, vorausgesetzt, er verfügt über die Intelligenz, Array-Element-Arithmetik immer noch richtig durchzuführen (so dass er beispielsweise weiß, dass a1 1K ist weg von a[0] und könnte sich darauf einstellen).

Der Grund, warum Sie möglicherweise unterschiedliche Ergebnisse erhalten, liegt darin, dass der Stapel zwar verkleinert werden kann, um “Objekte” hinzuzufügen, das Array jedoch ein einzelnes “Objekt” ist und möglicherweise aufsteigende Array-Elemente in der entgegengesetzten Reihenfolge enthält. Aber es ist nicht sicher, sich auf dieses Verhalten zu verlassen, da sich die Richtung ändern kann und Variablen aus einer Vielzahl von Gründen ausgetauscht werden können, einschließlich, aber nicht beschränkt auf:

  • Optimierung.
  • Ausrichtung.
  • die Launen der Person der Stapelverwaltungsteil des Compilers.

Siehe hier für meine ausgezeichnete Abhandlung über Stapelrichtung 🙂

Als Antwort auf Ihre konkreten Fragen:

  1. Wächst der Stack nach oben oder unten?
    Es spielt überhaupt keine Rolle (in Bezug auf den Standard), aber da Sie gefragt haben, kann es erwachsen werden oder je nach Implementierung im Speicher abgelegt.
  2. Was passiert zwischen a[2] und q Speicheradressen? Warum gibt es da einen großen Speicherunterschied? (20 Bytes)?
    Es spielt überhaupt keine Rolle (in Bezug auf den Standard). Siehe oben für mögliche Gründe.

Auf einem x86 besteht die “Speicherzuweisung” eines Stapelrahmens einfach aus dem Subtrahieren der erforderlichen Anzahl von Bytes vom Stapelzeiger (ich glaube, andere Architekturen sind ähnlich). In diesem Sinne schätze ich, dass der Stack “nach unten” wächst, indem die Adressen immer kleiner werden, wenn Sie tiefer in den Stack aufrufen (aber ich stelle mir den Speicher immer so vor, dass er mit 0 oben links beginnt und größere Adressen erhält, wenn Sie sich bewegen nach rechts und nach unten wickeln, so wächst in meiner Vorstellung der Stapel nach oben…). Die Reihenfolge der deklarierten Variablen hat möglicherweise keinen Einfluss auf ihre Adressen – ich glaube, der Standard erlaubt es dem Compiler, sie neu zu ordnen, solange dies keine Nebenwirkungen verursacht (korrigiert mich bitte jemand, wenn ich falsch liege). . Sie stecken einfach irgendwo in dieser Lücke in den verwendeten Adressen, die erstellt werden, wenn die Anzahl der Bytes vom Stapelzeiger subtrahiert wird.

Die Lücke um das Array kann eine Art Polsterung sein, aber es ist mysteriös für mich.

  • Tatsächlich, ich kennt der Compiler kann sie neu anordnen, da es ihm auch freisteht, sie überhaupt nicht zuzuweisen. Es kann sie einfach in Register ablegen und keinerlei Stapelspeicherplatz verwenden.

    – rméador

    4. November 2009 um 23:43 Uhr

  • Es kann sie nicht in die Register aufnehmen, wenn Sie auf ihre Adressen verweisen.

    – Gulden

    4. November 2009 um 23:57 Uhr

  • guter Punkt, hatte ich nicht bedacht. aber es reicht immer noch als Beweis dafür, dass der Compiler sie neu ordnen kann, da wir wissen, dass er dies zumindest manchmal tun kann 🙂

    – rméador

    4. November 2009 um 23:58 Uhr

Benutzeravatar von Prateek Khurana
Prateek Khurana

Zuallererst seine 8 Bytes ungenutzter Speicherplatz im Speicher (es sind nicht 12, denken Sie daran, dass der Stapel nach unten wächst, also ist der nicht zugewiesene Speicherplatz von 604 bis 597). und warum?. Weil jeder Datentyp Platz im Speicher benötigt, beginnend mit der Adresse, die durch seine Größe teilbar ist. In unserem Fall nimmt ein Array aus 3 Ganzzahlen 12 Bytes Speicherplatz ein und 604 ist nicht durch 12 teilbar. Es lässt also Leerzeichen, bis es auf eine Speicheradresse stößt, die durch 12 teilbar ist, nämlich 596.

Der dem Array zugewiesene Speicherplatz liegt also zwischen 596 und 584. Da die Array-Zuweisung jedoch fortgesetzt wird, beginnt das erste Element des Arrays bei der Adresse 584 und nicht bei 596.

  • Tatsächlich, ich kennt der Compiler kann sie neu anordnen, da es ihm auch freisteht, sie überhaupt nicht zuzuweisen. Es kann sie einfach in Register ablegen und keinerlei Stapelspeicherplatz verwenden.

    – rméador

    4. November 2009 um 23:43 Uhr

  • Es kann sie nicht in die Register aufnehmen, wenn Sie auf ihre Adressen verweisen.

    – Gulden

    4. November 2009 um 23:57 Uhr

  • guter Punkt, hatte ich nicht bedacht. aber es reicht immer noch als Beweis dafür, dass der Compiler sie neu ordnen kann, da wir wissen, dass er dies zumindest manchmal tun kann 🙂

    – rméador

    4. November 2009 um 23:58 Uhr

nach unten wächst, und dies liegt am Little-Endian-Byte-Order-Standard, wenn es um den Datensatz im Speicher geht.

Eine Möglichkeit, wie Sie es betrachten könnten, ist, dass der Stapel nach oben wächst, wenn Sie den Speicher von oben von 0 und von unten auf max betrachten.

Der Grund dafür, dass der Stack-Zeiger nach unten wächst, besteht darin, aus der Perspektive des Stack- oder Basiszeigers dereferenzieren zu können.

Denken Sie daran, dass die Dereferenzierung jeglicher Art von der niedrigsten zur höchsten Adresse zunimmt. Da der Stack nach unten wächst (von der höchsten zur niedrigsten Adresse), können Sie den Stack wie einen dynamischen Speicher behandeln.

Dies ist einer der Gründe, warum so viele Programmier- und Skriptsprachen eine stapelbasierte virtuelle Maschine anstelle einer registerbasierten verwenden.

  • The reason for the stack growing downward is to be able to dereference from the perspective of the stack or base pointer. Sehr schöne Begründung

    – Benutzer3405291

    17. November 2017 um 16:45 Uhr

1420500cookie-checkWächst der Stack nach oben oder nach unten?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy