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
Ich sehe also , dass die Speicheradressen von a
bis a[2]
um jeweils 4 Byte zunehmen. Von q
bis s
verringern sich die Speicheradressen jedoch um 4 Byte.
Ich frage mich 2 Dinge:
- Wächst der Stapel nach oben oder unten? (In diesem Fall sieht es für mich nach beidem aus)
- Was passiert zwischen
a[2]
undq
Speicheradressen? Warum gibt es dort einen großen Gedächtnisunterschied? (20 Bytes).
Hinweis: Dies ist keine Hausaufgabenfrage. Ich bin gespannt, wie Stack funktioniert. Vielen Dank für jede Hilfe.
Antworten:
Das Verhalten des Stapels (Auf- oder Abwachsen) hängt von der Anwendungsbinärschnittstelle (ABI) und der Organisation des Aufrufstapels (auch als Aktivierungsdatensatz bezeichnet) ab.
Während seiner gesamten Lebensdauer muss ein Programm mit anderen Programmen wie dem Betriebssystem kommunizieren. ABI bestimmt, wie ein Programm mit einem anderen Programm kommunizieren kann.
Der Stapel für verschiedene Architekturen kann in beide Richtungen wachsen, für eine Architektur ist er jedoch konsistent. Bitte überprüfen Sie dies Wiki-Link. Das Wachstum des Stacks wird jedoch vom ABI dieser Architektur bestimmt.
Wenn Sie beispielsweise den MIPS ABI verwenden, wird der Aufrufstapel wie folgt definiert.
Betrachten wir, dass die Funktion 'fn1' 'fn2' aufruft. Nun ist der Stapelrahmen von 'fn2' aus wie folgt:
Jetzt können Sie sehen, dass der Stapel nach unten wächst. Wenn die Variablen also dem lokalen Rahmen der Funktion zugewiesen werden, wachsen die Adressen der Variablen tatsächlich nach unten. Der Compiler kann die Reihenfolge der Variablen für die Speicherzuordnung festlegen. (In Ihrem Fall kann entweder 'q' oder 's' zuerst der Stapelspeicher zugewiesen werden. Im Allgemeinen führt der Compiler die Stapelspeicherzuweisung jedoch gemäß der Reihenfolge der Deklaration der Variablen durch.)
Bei den Arrays hat die Zuordnung jedoch nur einen einzigen Zeiger, und der zuzuweisende Speicher wird tatsächlich von einem einzelnen Zeiger angezeigt. Der Speicher muss für ein Array zusammenhängend sein. Obwohl der Stapel nach unten wächst, wächst der Stapel für Arrays nach oben.
quelle
Das sind eigentlich zwei Fragen. Zum einen geht es darum, wie der Stapel wächst, wenn eine Funktion eine andere aufruft (wenn ein neuer Frame zugewiesen wird), und zum anderen geht es darum, wie Variablen im Frame einer bestimmten Funktion angeordnet sind.
Beides ist nicht im C-Standard festgelegt, aber die Antworten sind etwas anders:
f
der Frame-Zeiger größer oder kleiner alsg
der Frame-Zeiger? Dies kann in beide Richtungen gehen - es hängt vom jeweiligen Compiler und der jeweiligen Architektur ab (siehe "Aufrufkonvention"), ist jedoch innerhalb einer bestimmten Plattform immer konsistent (mit einigen bizarren Ausnahmen, siehe Kommentare). Abwärts ist häufiger; Dies ist bei x86-, PowerPC-, MIPS-, SPARC-, EE- und Cell-SPUs der Fall.quelle
Die Richtung, in die Stapel wachsen, ist architekturspezifisch. Meines Wissens nach haben jedoch nur sehr wenige Hardwarearchitekturen Stapel, die erwachsen werden.
Die Richtung, in die ein Stapel wächst, ist unabhängig vom Layout eines einzelnen Objekts. Während der Stapel möglicherweise verkleinert ist, werden Arrays nicht (dh & array [n] ist immer <& array [n + 1]);
quelle
Der Standard enthält nichts, was vorschreibt, wie die Dinge auf dem Stapel überhaupt organisiert sind. Tatsächlich könnten Sie einen konformen Compiler erstellen, der Array-Elemente überhaupt nicht an zusammenhängenden Elementen auf dem Stapel speichert, vorausgesetzt, er verfügt über die Intelligenz, um Array-Element-Arithmetik weiterhin ordnungsgemäß auszuführen (sodass er beispielsweise wusste, dass eine 1 war 1K von einer [0] entfernt und könnte sich darauf einstellen).
Der Grund, warum Sie möglicherweise unterschiedliche Ergebnisse erhalten, liegt darin, dass der Stapel zwar nach unten wächst, um "Objekte" hinzuzufügen, das Array jedoch ein einzelnes "Objekt" ist und aufsteigende Array-Elemente in umgekehrter Reihenfolge aufweist. Es ist jedoch nicht sicher, sich auf dieses Verhalten zu verlassen, da sich die Richtung ändern kann und Variablen aus verschiedenen Gründen ausgetauscht werden können, einschließlich, aber nicht beschränkt auf:
Siehe hier für meine ausgezeichnete Abhandlung über die Stapelrichtung :-)
Als Antwort auf Ihre spezifischen Fragen:
Es spielt überhaupt keine Rolle (in Bezug auf den Standard), aber da Sie gefragt haben, kann es je nach Implementierung im Speicher nach oben oder unten wachsen .
Es spielt überhaupt keine Rolle (in Bezug auf den Standard). Siehe oben für mögliche Gründe.
quelle
Auf einem x86 besteht die Speicher- "Zuordnung" eines Stapelrahmens einfach darin, die erforderliche Anzahl von Bytes vom Stapelzeiger zu subtrahieren (ich glaube, andere Architekturen sind ähnlich). In diesem Sinne denke ich, dass der Stapel "verkleinert" wird, indem die Adressen zunehmend kleiner werden, wenn Sie tiefer in den Stapel aufrufen (aber ich stelle mir immer vor, dass der Speicher mit 0 oben links beginnt und größere Adressen erhält, wenn Sie sich bewegen nach rechts und nach unten wickeln, so dass in meinem mentalen Bild der Stapel wächst ...). 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 (jemand korrigiert mich bitte, wenn ich falsch liege). . Sie'
Die Lücke um das Array mag eine Art Polsterung sein, aber es ist für mich mysteriös.
quelle
Zuallererst wachsen die 8 Bytes nicht genutzten Speicherplatz im Speicher (nicht 12, denken Sie daran, dass der Stapel nach unten wächst, sodass der nicht zugewiesene Speicherplatz zwischen 604 und 597 liegt). und warum?. Weil jeder Datentyp ab der durch seine Größe teilbaren Adresse Speicherplatz belegt. In unserem Fall benötigt ein Array mit 3 Ganzzahlen 12 Byte Speicherplatz und 604 ist nicht durch 12 teilbar. Es bleiben also leere Leerzeichen, bis es auf eine Speicheradresse trifft, die durch 12 teilbar ist. Es ist 596.
Der dem Array zugewiesene Speicherplatz beträgt also 596 bis 584. Da die Array-Zuweisung jedoch fortgesetzt wird, beginnt das erste Element des Arrays mit der Adresse 584 und nicht mit 596.
quelle
Dem Compiler steht es frei, lokale (Auto-) Variablen an einer beliebigen Stelle im lokalen Stapelrahmen zuzuweisen. Sie können die Richtung des Stapelwachstums nicht zuverlässig daraus ableiten. Sie können die Richtung des Stapelwachstums durch Vergleichen der Adressen verschachtelter Stapelrahmen ableiten, dh durch Vergleichen der Adresse einer lokalen Variablen innerhalb des Stapelrahmens einer Funktion im Vergleich zu ihrer Angerufenen:
quelle
Ich denke nicht, dass es so deterministisch ist. Das a-Array scheint zu "wachsen", da dieser Speicher zusammenhängend zugewiesen werden sollte. Da q und s jedoch überhaupt nicht miteinander verwandt sind, steckt der Compiler jeden von ihnen einfach an einen beliebigen freien Speicherplatz innerhalb des Stapels, wahrscheinlich diejenigen, die am besten zu einer ganzzahligen Größe passen.
Was zwischen a [2] und q passiert ist, ist, dass der Raum um qs Position nicht groß genug war (dh nicht größer als 12 Bytes), um ein 3-Integer-Array zuzuweisen.
quelle
Mein Stapel scheint sich zu Adressen mit niedrigerer Nummer zu erstrecken.
Auf einem anderen Computer oder sogar auf meinem eigenen Computer kann dies anders sein, wenn ich einen anderen Compileraufruf verwende. ... oder der Compiler muss sich dafür entscheiden, überhaupt keinen Stack zu verwenden (alles inline (Funktionen und Variablen, wenn ich nicht die Adresse von ihnen genommen habe)).
quelle
Der Stapel wächst nach unten (auf x86). Der Stapel wird jedoch beim Laden der Funktion in einem Block zugewiesen, und Sie können nicht garantieren, in welcher Reihenfolge sich die Elemente auf dem Stapel befinden.
In diesem Fall wurde Platz für zwei Ints und ein Drei-Int-Array auf dem Stapel zugewiesen. Es hat auch zusätzliche 12 Bytes nach dem Array zugewiesen, also sieht es so aus:
a [12 Bytes]
Auffüllen (?) [12 Bytes]
s [4 Bytes]
q [4 Bytes]
Aus irgendeinem Grund hat Ihr Compiler entschieden, dass für diese Funktion 32 Byte und möglicherweise mehr zugewiesen werden müssen. Das ist für Sie als C-Programmierer undurchsichtig, Sie wissen nicht warum.
Wenn Sie wissen möchten, warum, kompilieren Sie den Code in Assemblersprache. Ich glaube, dass es -S auf gcc und / S auf dem C-Compiler von MS ist. Wenn Sie sich die Eröffnungsanweisungen für diese Funktion ansehen, sehen Sie, dass der alte Stapelzeiger gespeichert und dann 32 (oder etwas anderes!) Von ihm subtrahiert werden. Von dort aus können Sie sehen, wie der Code auf diesen 32-Byte-Speicherblock zugreift, und herausfinden, was Ihr Compiler tut. Am Ende der Funktion sehen Sie, wie der Stapelzeiger wiederhergestellt wird.
quelle
Dies hängt von Ihrem Betriebssystem und Ihrem Compiler ab.
quelle
Der Stapel wächst nach unten. Also f (g (h ())), der für h zugewiesene Stapel beginnt an einer niedrigeren Adresse als g und g ist niedriger als f. Variablen innerhalb des Stapels müssen jedoch der C-Spezifikation folgen.
http://c0x.coding-guidelines.com/6.5.8.html
1206 Wenn die Objekte, auf die verwiesen wird, Mitglieder desselben Aggregatobjekts sind, werden Zeiger auf später deklarierte Strukturelemente größer als Zeiger auf Elemente, die früher in der Struktur deklariert wurden, und Zeiger auf Array-Elemente mit größeren tiefgestellten Werten verglichen mehr als Zeiger auf Elemente desselben Array mit niedrigeren tiefgestellten Werten.
& a [0] <& a [1] muss immer wahr sein, unabhängig davon, wie 'a' zugewiesen wird
quelle
wächst nach unten und dies liegt am Standard der kleinen Endian-Bytereihenfolge, wenn es um den Datensatz im Speicher geht.
Eine Möglichkeit, dies zu betrachten, besteht darin, dass der Stapel nach oben wächst, wenn Sie den Speicher von 0 von oben und max von unten betrachten.
Der Grund dafür, dass der Stapel nach unten wächst, besteht darin, dass er aus der Perspektive des Stapels oder Basiszeigers dereferenziert werden kann.
Denken Sie daran, dass die Dereferenzierung jeglicher Art von der niedrigsten zur höchsten Adresse zunimmt. Da der Stapel nach unten wächst (höchste bis niedrigste Adresse), können Sie den Stapel wie einen dynamischen Speicher behandeln.
Dies ist ein Grund, warum so viele Programmier- und Skriptsprachen eine stapelbasierte virtuelle Maschine anstelle einer registergestützten verwenden.
quelle
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 ArgumentationDas hängt von der Architektur ab. Verwenden Sie diesen Code von GeeksForGeeks, um Ihr eigenes System zu überprüfen :
quelle