2017-07-22 5 views
0

Ich versuche, Elemente auf einem Stapel besser zu verstehen und wie sie angesprochen werden. Der Artikel, den ich gefunden here scheint anzuzeigen, dass, wenn MIPS-Stack initialisiert wird, eine feste Menge an Speicher zugeordnet ist und der Stapel bis zum Stack-Limit wächst, die als kleinere Adressen erscheinen. Ich würde annehmen, dass basierend auf dieser Logik ein Stack-Überlauf auftreten würde, wenn 0x0000 durchlaufen wurde?Verwirrung über Stapelwachstum und Adressierung

Ich weiß, MIPS ist Big Endian, aber ändert sich das, wie der Stack wächst? Ich schrieb, was ich glaubte, wäre eine schnelle Möglichkeit, dies auf einer x86_64 Maschine zu beobachten, aber der Stapel scheint zu wachsen, wie ich ursprünglich angenommen hatte.

#include <iostream> 
#include <vector> 
int main() { 
    std::vector<int*> v; 
    for(int i = 0; i < 10; i++) { 
     v.push_back(new int); 
     std::cout << v.back() << std::endl; 
    } 
} 

Ich bin auch durch die Tatsache verwirrt, dass nicht alle der Speicheradresse des nicht zusammenhängend zu sein scheinen, was mich denken lässt, habe ich etwas dumm. Könnte jemand bitte klarstellen?

+3

Dieses Programm weist Daten auf dem Heap und nicht auf dem Stack zu. – interjay

+0

Oh, ist das wegen der Tatsache, dass ich Vektoren verwende? würde ein Zeichenarray auf dem Stapel sein? – mreff555

+2

Sowohl die Daten des Vektors als auch die Zuordnungen von 'new int' befinden sich auf dem Heap. Nur lokale Variablen selbst (ohne eingeschlossene innere Daten wie die Vektoren) befinden sich auf dem Stapel. – interjay

Antwort

1

im Wesentlichen gibt es 3 Arten von Speicher, den Sie in der Programmierung verwenden: statische, dynamische/Heap und Stack.

Der statische Speicher wird vom Compiler vorab zugewiesen und besteht aus den Konstanten und Variablen, die in Ihrem Programm statisch deklariert sind.

Heap ist der Speicher, die Sie frei zuordnen können und lassen

Stapel der Speicher, die in einer Funktion deklariert für alle lokalen Variablen zugewiesen wird. Dies ist wichtig, da jedes Mal, wenn Sie die Funktion aufrufen, ein neuer Speicher für ihre Variablen zugewiesen wird. Jeder Aufruf einer Funktion stellt sicher, dass sie eine eigene Kopie der Variablen besitzt. Und jedes Mal, wenn Sie von der Funktion zurückkehren, wird der Speicher freigegeben.

Es ist absolut egal, wie der Stapel verwaltet wird, sobald er den obigen Regeln folgt. Es ist jedoch günstig, Programmspeicher im unteren Adressraum zuzuordnen und aufzuwachsen und den Stapel von einem oberen Speicherplatz zu starten und nach unten zu wachsen. Die meisten Systeme implementieren dieses Schema.

Im Allgemeinen gibt es ein Stack-Pointer-Register/Variable, die so auf die aktuelle Stack-Adresse zeigt. Wenn eine Funktion aufgerufen wird, verringert sie diese Adresse um die Anzahl der Bytes, die sie für ihre Variablen benötigt. Wenn es die nächste Funktion aufruft, wird diese neue beginnen, wobei der neue Zeiger vom Aufrufer bereits verringert wird. Wenn die Funktion zurückgibt, stellt sie den Zeiger wieder her, von dem sie gestartet wurde.

Es könnte verschiedene Schemata geben, aber soweit ich weiß, Mips und i86 folgen diesem.

Und im Wesentlichen gibt es nur einen virtuellen Speicherplatz im Programm. Dies hängt vom Betriebssystem und/oder Compiler ab.Der Compiler wird den Speicher in den logischen Bereichen für seinen eigenen Gebrauch aufteilen und hoffentlich entsprechend den in den Plattformdokumenten definierten Aufrufkonventionen behandeln.

Also, in unserem Beispiel sind v und i auf dem Funktionsstapel zugeordnet. cout ist statisch. Jeder new int Speicherplatz in Heap zuweisen. v ist keine einfache Variable, sondern eine Struktur, die Felder enthält, die zum Verwalten der Liste benötigt werden. es braucht Platz für all diese Interna. So ändert jede push_back diese Felder in gewisser Weise auf das zugewiesene "int". push_back() und back() sind Funktionsaufrufe und ordnen ihre eigenen Stapel für interne Variablen zu, um die obere Funktion nicht zu stören.

2

Der Stack auf x86-Maschinen wächst ebenfalls nach unten. Die Endianz hängt nicht mit der Richtung zusammen, in der der Stapel wächst.

Der Stapel einer Maschine hat absolut nichts mit std::vector<> zu tun. Außerdem teilt new int Heap-Speicher zu, so dass es Ihnen absolut nichts über den Stack sagt.

Um zu sehen, welche Richtung der Stapel wächst, müssen Sie so etwas wie dies tun:

recursive(5); 

void recursive(int n) 
{ 
    if(n == 0) 
     return; 
    int a; 
    printf("%p\n", &a); 
    recursive(n - 1); 
} 

(Beachten Sie, dass, wenn Ihr Compiler ist intelligent genug, Endrekursion zu optimieren, dann müssen Sie sagen, Es nicht optimieren Sie es, sonst sind die Beobachtungen alle falsch.)

+0

für eine x86_64 Ganzzahl Ich nahm an, dass der Unterschied in den Adressen in meinem Beispiel 32 Bit oder 0x20 sein sollte, aber wenn ich das Programm kompiliere und ausführe, sind die meisten Adressen 32 Bit auseinander, aber nicht alle. – mreff555

+1

@ mref555: Es liegt daran, dass es andere Dinge im Stapel gibt, zum Beispiel die Rückgabeadresse für die Funktion. Und der Stapel wird normalerweise ausgerichtet, sodass möglicherweise zusätzliche Polsterung erforderlich ist. Und vergiss Bits hier :) Wir berechnen in Bytes, wenn wir über Unterschiede zwischen Speicheradressen sprechen. – geza

+0

Das macht Sinn, aber mir wird gesagt, was ich geschrieben habe, ist auf dem Haufen. so oder so, ich denke, das macht Sinn. Ich dachte nur, Vektoren wären zusammenhängend. Ich habe nur Bits benutzt, weil ich gerade eine schnelle Umwandlung in meinem Kopf gemacht habe. – mreff555