Frage Was und wo sind der Stapel und der Haufen?


Programmiersprachenbücher erklären, dass Werttypen auf dem erstellt werden Stapelund Referenztypen werden auf dem erstellt Haufen, ohne zu erklären, was diese zwei Dinge sind. Ich habe keine klare Erklärung dafür gelesen. Ich verstehe was ein Stapel ist. Aber,

  • Wo und was sind sie (physisch in einem echten Computerspeicher)?
  • In welchem ​​Umfang werden sie von der Betriebssystem- oder Sprachlaufzeit gesteuert?
  • Was ist ihr Umfang?
  • Was bestimmt die Größe von jedem von ihnen?
  • Was macht einen schneller?

7116
2017-09-17 04:18


Ursprung


Antworten:


Der Stapel ist der Speicher, der als Arbeitsspeicher für einen Ausführungsthread reserviert ist. Wenn eine Funktion aufgerufen wird, ist ein Block oben auf dem Stapel für lokale Variablen und einige Buchhaltungsdaten reserviert. Wenn diese Funktion zurückkehrt, wird der Block unbenutzt und kann beim nächsten Aufruf einer Funktion verwendet werden. Der Stapel ist immer in einer LIFO-Reihenfolge (last in first out) reserviert; Der zuletzt reservierte Block ist immer der nächste freizugebende Block. Dies macht es sehr einfach, den Stack zu verfolgen; Einen Block vom Stapel zu befreien, ist nichts anderes als einen Zeiger einzustellen.

Der Heap ist Speicher für die dynamische Zuordnung reserviert. Im Gegensatz zum Stapel gibt es kein erzwungenes Muster für die Zuweisung und Freigabe von Blöcken vom Heap. Sie können jederzeit einen Block zuweisen und ihn jederzeit freigeben. Dies macht es viel komplizierter, zu verfolgen, welche Teile des Heaps zu irgendeinem gegebenen Zeitpunkt zugewiesen oder frei sind; Es gibt viele benutzerdefinierte Heap-Allokatoren, um die Heap-Leistung für verschiedene Verwendungsmuster anzupassen.

Jeder Thread erhält einen Stack, während es in der Regel nur einen Heap für die Anwendung gibt (obwohl es nicht ungewöhnlich ist, mehrere Heaps für verschiedene Zuweisungstypen zu haben).

Um Ihre Fragen direkt zu beantworten:

In welchem ​​Umfang werden sie von der Betriebssystem- oder Sprachlaufzeit gesteuert?

Das Betriebssystem weist den Stack für jeden Thread auf Systemebene zu, wenn der Thread erstellt wird. In der Regel wird das Betriebssystem von der Sprachenlaufzeit aufgerufen, um den Heap für die Anwendung zuzuweisen.

Was ist ihr Umfang?

Der Stapel wird an einen Thread angehängt. Wenn der Thread beendet wird, wird der Stapel zurückgewonnen. Der Heap wird normalerweise beim Start der Anwendung von der Laufzeit zugewiesen und wird zurückgewonnen, wenn die Anwendung (der technische Prozess) beendet wird.

Was bestimmt die Größe von jedem von ihnen? 

Die Größe des Stapels wird festgelegt, wenn ein Thread erstellt wird. Die Größe des Heapspeichers wird beim Start der Anwendung festgelegt, kann jedoch bei Bedarf erhöht werden (der Zuordner fordert mehr Speicher vom Betriebssystem an).

Was macht einen schneller?

Der Stapel ist schneller, weil es das Zugriffsmuster trivial macht, Speicher von ihm zuzuordnen und zu entziehen (ein Zeiger / Integer wird einfach inkrementiert oder dekrementiert), während der Heap eine viel komplexere Buchführung bei einer Zuweisung oder Freigabe hat. Außerdem neigt jedes Byte im Stapel dazu, sehr häufig wiederverwendet zu werden, was bedeutet, dass es dazu neigt, auf den Cache des Prozessors abgebildet zu werden, was es sehr schnell macht. Ein weiterer Leistungshit für den Heap ist, dass der Heap, der hauptsächlich eine globale Ressource ist, typischerweise Multi-Threading-sicher sein muss, d. H. Jede Zuweisung und Freigabe muss - typischerweise - mit "allen" anderen Heap-Zugriffen im Programm synchronisiert werden.

Eine klare Demonstration:
Bildquelle: vikashazrati.wordpress.com


5228
2017-09-17 04:52



Stapel:

  • Gespeichert in Computer-RAM wie der Heap.
  • Variablen, die auf dem Stapel erstellt wurden, werden nicht mehr verwendet und automatisch freigegeben.
  • Viel schneller im Vergleich zu Variablen auf dem Heap.
  • Implementiert mit einer tatsächlichen Stapeldatenstruktur.
  • Speichert lokale Daten, gibt Adressen zurück und wird für die Parameterübergabe verwendet.
  • Ein Stack kann überlaufen, wenn zu viele Stacks verwendet werden (meist aus unendlicher oder zu tiefer Rekursion, sehr große Allokationen).
  • Daten, die auf dem Stapel erstellt wurden, können ohne Zeiger verwendet werden.
  • Sie würden den Stapel verwenden, wenn Sie genau wissen, wie viele Daten Sie vor der Kompilierungszeit reservieren müssen, und sie ist nicht zu groß.
  • In der Regel wird eine maximale Größe bereits beim Start des Programms festgelegt.

Haufen:

  • Gespeichert in Computer-RAM wie der Stapel.
  • In C ++ müssen Variablen auf dem Heap manuell zerstört werden und fallen niemals außerhalb des Gültigkeitsbereichs. Die Daten werden mit freigegeben delete, delete[], oder free.
  • Langsamer im Vergleich zu Variablen auf dem Stack.
  • Wird bei Bedarf zur Zuweisung eines Datenblocks für die Verwendung durch das Programm verwendet.
  • Kann Fragmentierung aufweisen, wenn viele Zu- und Abmeldungen vorgenommen werden.
  • In C ++ oder C werden auf dem Heap erstellte Daten durch Zeiger angezeigt und mit zugeordnet new oder malloc beziehungsweise.
  • Kann Zuordnungsfehler haben, wenn ein zu großer Puffer angefordert wird.
  • Sie würden den Heap verwenden, wenn Sie nicht genau wissen, wie viele Daten Sie zur Laufzeit benötigen oder ob Sie viele Daten zuweisen müssen.
  • Verantwortlich für Speicherlecks.

Beispiel:

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;

2092
2017-09-17 04:20



Der wichtigste Punkt ist, dass Heap und Stack generische Begriffe für die Zuweisung von Speicher sind. Sie können auf viele verschiedene Arten implementiert werden, und die Begriffe gelten für die grundlegenden Konzepte.

  • In einem Stapel von Gegenständen sitzen die Gegenstände in der Reihenfolge, in der sie dort platziert wurden, übereinander, und Sie können nur die oberste entfernen (ohne das Ganze umzustürzen).

    Stack like a stack of papers

    Die Einfachheit eines Stapels besteht darin, dass Sie keine Tabelle verwalten müssen, die einen Datensatz für jeden Abschnitt des zugewiesenen Speichers enthält. Die einzige Statusinformation, die Sie benötigen, ist ein einzelner Zeiger auf das Ende des Stapels. Zum Zuweisen und Aufheben der Zuweisung erhöhen und dekrementieren Sie einfach diesen einzelnen Zeiger. Hinweis: Ein Stapel kann manchmal so implementiert werden, dass er am Anfang eines Speicherbereichs beginnt und sich nach unten erstreckt, anstatt nach oben zu wachsen.

  • In einem Heap gibt es keine besondere Reihenfolge für die Art, wie Elemente platziert werden. Sie können Objekte in beliebiger Reihenfolge erreichen und entfernen, da es keinen eindeutigen "Top" -Eintrag gibt.

    Heap like a heap of licorice allsorts

    Die Heapzuweisung erfordert die Beibehaltung einer vollständigen Aufzeichnung darüber, welcher Speicher zugeordnet ist und was nicht, sowie eine gewisse Overhead-Wartung, um die Fragmentierung zu reduzieren, zusammenhängende Speichersegmente zu finden, die groß genug sind, um der angeforderten Größe zu entsprechen, und so weiter. Der Speicher kann jederzeit freigegeben werden, wobei freier Speicherplatz verbleibt. Manchmal führt ein Speicherzuordner Wartungsaufgaben aus, z. B. das Defragmentieren von Speicher durch Verschieben von zugewiesenem Speicher oder das Sammeln von Datenmüll - zur Laufzeit identifizieren, wenn Speicher nicht mehr im Bereich ist und die Zuordnung aufgehoben wird.

Diese Bilder sollten ziemlich gut die zwei Arten der Speicherzuweisung und -freigabe in einem Stapel und einem Heap beschreiben. Mist!

  • In welchem ​​Umfang werden sie von der Betriebssystem- oder Sprachlaufzeit gesteuert?

    Wie erwähnt, sind Heap und Stack allgemeine Begriffe und können auf viele Arten implementiert werden. Computerprogramme haben typischerweise einen Stapel namens a Anrufliste die Informationen speichert, die für die aktuelle Funktion relevant sind, z. B. einen Zeiger auf die Funktion, von der sie aufgerufen wurde, und alle lokalen Variablen. Da Funktionen andere Funktionen aufrufen und dann zurückgeben, wächst und schrumpft der Stapel, um Informationen von den Funktionen weiter unten im Aufruf-Stack zu halten. Ein Programm hat keine Laufzeitkontrolle über es; Es wird von der Programmiersprache, dem Betriebssystem und sogar der Systemarchitektur bestimmt.

    Ein Heap ist ein allgemeiner Begriff für jeden Speicher, der dynamisch und zufällig zugewiesen wird. d.h. außer Betrieb. Der Speicher wird normalerweise vom Betriebssystem zugewiesen, wobei die Anwendung API-Funktionen aufruft, um diese Zuordnung vorzunehmen. Für die Verwaltung von dynamisch zugewiesenem Speicher, der normalerweise vom Betriebssystem verarbeitet wird, ist ein gewisser Aufwand erforderlich.

  • Was ist ihr Umfang?

    Der Call-Stack ist ein solches Low-Level-Konzept, dass es sich nicht auf "Scope" im Sinne der Programmierung bezieht. Wenn Sie etwas Code zerlegen, sehen Sie relative Pointer-Stil-Verweise auf Teile des Stapels, aber soweit eine höhere Ebene betroffen ist, erlegt die Sprache ihre eigenen Regeln für den Umfang auf. Ein wichtiger Aspekt eines Stapels ist jedoch, dass sobald eine Funktion zurückkehrt, alles, was lokal für diese Funktion ist, sofort vom Stapel freigegeben wird. Das funktioniert so, wie Sie es angesichts der Funktionsweise Ihrer Programmiersprachen erwarten würden. In einem Haufen ist es auch schwierig zu definieren. Der Bereich ist, was auch immer vom Betriebssystem bereitgestellt wird, aber Ihre Programmiersprache fügt wahrscheinlich Regeln hinzu, was ein "Bereich" in Ihrer Anwendung ist. Die Prozessorarchitektur und das Betriebssystem verwenden eine virtuelle Adressierung, die der Prozessor in physikalische Adressen übersetzt, und es gibt Seitenfehler usw. Sie verfolgen, welche Seiten zu welchen Anwendungen gehören. Sie müssen sich darüber aber nie wirklich Gedanken machen, weil Sie einfach jede Methode verwenden, die Ihre Programmiersprache verwendet, um Speicher zuzuordnen und freizugeben und nach Fehlern zu suchen (wenn die Zuweisung / Freigabe aus irgendeinem Grund fehlschlägt).

  • Was bestimmt die Größe von jedem von ihnen?

    Auch hier kommt es auf die Sprache, den Compiler, das Betriebssystem und die Architektur an. Ein Stack wird normalerweise vorbelegt, weil per Definition zusammenhängender Speicher sein muss (mehr dazu im letzten Absatz). Der Sprachencompiler oder das Betriebssystem bestimmen seine Größe. Sie speichern keine großen Datenblöcke auf dem Stapel, so dass es groß genug ist, dass es nie vollständig verwendet werden sollte, außer in Fällen von unerwünschter endloser Rekursion (daher "Stapelüberlauf") oder anderen ungewöhnlichen Programmierentscheidungen.

    Ein Heap ist ein allgemeiner Begriff für alles, was dynamisch zugewiesen werden kann. Je nachdem, wie Sie es betrachten, ändert sich die Größe ständig. In modernen Prozessoren und Betriebssystemen ist die genaue Funktionsweise ohnehin sehr abstrahiert, so dass Sie sich normalerweise nicht viel Gedanken darüber machen müssen, wie es funktioniert, außer dass Sie (in Sprachen, in denen es Ihnen erlaubt) keinen Speicher verwenden müssen Sie haben noch nicht zugewiesen oder Speicher, den Sie freigegeben haben.

  • Was macht einen schneller?

    Der Stapel ist schneller, da der gesamte freie Speicher immer zusammenhängend ist. Es muss keine Liste aller Segmente des freien Speichers gepflegt werden, sondern nur ein einziger Zeiger auf den aktuellen Anfang des Stapels. Compiler speichern diesen Zeiger normalerweise in einem speziellen, schnellen registrieren für diesen Zweck. Darüber hinaus konzentrieren sich nachfolgende Operationen auf einem Stapel in der Regel in sehr nahe gelegenen Bereichen des Speichers, was auf einem sehr niedrigen Niveau für die Optimierung durch die Prozessor-On-Die-Caches gut ist.


1259
2018-03-19 14:38



(Ich habe diese Antwort von einer anderen Frage, die mehr oder weniger ein Betrüger von diesem war, verschoben.)

Die Antwort auf Ihre Frage ist implementierungsspezifisch und kann je nach Compiler und Prozessorarchitektur variieren. Hier ist jedoch eine vereinfachte Erklärung.

  • Sowohl der Stack als auch der Heap sind Speicherbereiche, die vom zugrunde liegenden Betriebssystem zugewiesen werden (häufig virtueller Speicher, der bei Bedarf dem physischen Speicher zugeordnet wird).
  • In einer Multithread-Umgebung hat jeder Thread seinen eigenen, völlig unabhängigen Stack, aber sie teilen sich den Heap. Der gleichzeitige Zugriff muss auf dem Heap kontrolliert werden und ist auf dem Stack nicht möglich.

Der Haufen

  • Der Heap enthält eine verknüpfte Liste von verwendeten und freien Blöcken. Neue Zuordnungen auf dem Heap (durch new oder malloc) werden erfüllt, indem ein geeigneter Block aus einem der freien Blöcke erstellt wird. Dies erfordert eine Aktualisierung der Liste der Blöcke auf dem Heap. Dies Meta-Informationen über die Blöcke auf dem Heap wird auch auf dem Heap oft in einem kleinen Bereich direkt vor jedem Block gespeichert.
  • Wenn der Heap wächst, werden neue Blöcke oft von niedrigeren Adressen zu höheren Adressen zugewiesen. So kann man sich den Haufen als einen vorstellen Haufen von Speicherblöcken, deren Größe zunimmt, wenn Speicher zugewiesen wird. Wenn der Heap für eine Zuweisung zu klein ist, kann die Größe oft erhöht werden, indem mehr Speicher vom zugrunde liegenden Betriebssystem erworben wird.
  • Das Zuordnen und Freigeben vieler kleiner Blöcke kann den Stapel in einem Zustand verlassen, in dem viele kleine freie Blöcke zwischen den verwendeten Blöcken liegen. Eine Anforderung zum Zuweisen eines großen Blocks kann fehlschlagen, weil keiner der freien Blöcke groß genug ist, um die Zuordnungsanforderung zu erfüllen, obwohl die kombinierte Größe der freien Blöcke groß genug sein kann. Das nennt man Haufenfragmentierung.
  • Wenn ein benutzter Block, der an einen freien Block angrenzt, freigegeben wird, kann der neue freie Block mit dem benachbarten freien Block verschmolzen werden, um einen größeren freien Block zu erzeugen, wodurch die Fragmentierung des Heap effektiv reduziert wird.

The heap

Der Stapel

  • Der Stack arbeitet oft eng mit einem speziellen Register auf der CPU namens Stapelzeiger. Anfänglich zeigt der Stapelzeiger auf die oberste Ebene des Stapels (die höchste Adresse auf dem Stapel).
  • Die CPU hat spezielle Anweisungen für drängen Werte auf den Stapel und knallend sie zurück vom Stapel. Jeder drücken speichert den Wert an der aktuellen Position des Stapelzeigers und verringert den Stapelzeiger. EIN Pop ruft den Wert ab, auf den der Stapelzeiger zeigt, und erhöht dann den Stapelzeiger (lassen Sie sich nicht dadurch verwirren, dass hinzufügen ein Wert für den Stapel nimmt ab der Stapelzeiger und entfernen ein Wert erhöht sich es. Denken Sie daran, dass der Stapel nach unten wächst). Die gespeicherten und abgerufenen Werte sind die Werte der CPU-Register.
  • Wenn eine Funktion aufgerufen wird, verwendet die CPU spezielle Anweisungen, die den Strom drücken Befehlszeigerd.h. die Adresse des Codes, der auf dem Stapel ausgeführt wird. Die CPU springt dann zu der Funktion, indem sie die Taste Befehlszeiger auf die Adresse der aufgerufenen Funktion. Später, wenn die Funktion zurückkehrt, wird der alte Instruktionszeiger aus dem Stapel entnommen und die Ausführung wird bei dem Code unmittelbar nach dem Aufruf der Funktion fortgesetzt.
  • Wenn eine Funktion eingegeben wird, wird der Stapelzeiger verringert, um mehr Platz auf dem Stapel für lokale (automatische) Variablen zuzuweisen. Wenn die Funktion eine lokale 32-Bit-Variable hat, werden vier Bytes auf dem Stapel abgelegt. Wenn die Funktion zurückkehrt, wird der Stapelzeiger zurückbewegt, um den zugewiesenen Bereich freizugeben.
  • Wenn eine Funktion Parameter hat, werden diese vor dem Aufruf der Funktion auf den Stack geschoben. Der Code in der Funktion kann dann den Stapel vom aktuellen Stapelzeiger aus nach oben navigieren, um diese Werte zu finden.
  • Sammelfunktionsaufrufe funktionieren wie ein Zauber. Jeder neue Aufruf weist Funktionsparameter, die Rücksprungadresse und den Platz für lokale Variablen und diese zu Aktivierungsdatensätze kann für verschachtelte Aufrufe gestapelt werden und wird bei der Rückkehr der Funktionen auf die richtige Weise wiederhergestellt.
  • Da der Stapel ein begrenzter Speicherblock ist, können Sie a Paketüberfluss indem zu viele verschachtelte Funktionen aufgerufen werden und / oder zu viel Platz für lokale Variablen zugewiesen wird. Oft ist der für den Stack verwendete Speicherbereich so eingerichtet, dass das Schreiben unter die unterste Adresse des Stacks eine Trap oder Exception in der CPU auslöst. Diese Ausnahmebedingung kann dann von der Laufzeit abgefangen und in eine Art Stapelüberlauf-Ausnahme umgewandelt werden.

The stack

Kann eine Funktion auf dem Heap anstatt auf einem Stack zugewiesen werden?

Nein, Aktivierungsaufzeichnungen für Funktionen (d. H. Lokale oder automatische Variablen) werden auf dem Stapel zugewiesen, der nicht nur zum Speichern dieser Variablen verwendet wird, sondern auch um verschachtelte Funktionsaufrufe zu verfolgen.

Wie der Heap verwaltet wird, hängt wirklich von der Laufzeitumgebung ab. C benutzt malloc und C ++ verwendet new, aber viele andere Sprachen haben Müllsammlung.

Der Stapel ist jedoch ein Merkmal auf niedrigerer Ebene, das eng mit der Prozessorarchitektur verbunden ist. Das Erweitern des Heapspeichers, wenn nicht genügend Speicherplatz vorhanden ist, ist nicht zu schwer, da er in dem Bibliotheksaufruf implementiert werden kann, der den Heapspeicher verarbeitet. Es ist jedoch oft unmöglich, den Stack zu vergrößern, da der Stack-Überlauf nur entdeckt wird, wenn es zu spät ist; und das Herunterfahren des Thread der Ausführung ist die einzig mögliche Option.


663
2017-07-31 15:54



Im folgenden C # -Code

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

So wird der Speicher verwaltet

Picture of variables on the stack

Local Variables das dauert nur so lange, wie der Funktionsaufruf in den Stapel geht. Der Heap wird für Variablen verwendet, deren Lebensdauer wir nicht wirklich im Voraus kennen, aber wir erwarten, dass sie eine Weile dauern. In den meisten Sprachen ist es wichtig, dass wir zur Kompilierungszeit wissen, wie groß eine Variable ist, wenn wir sie auf dem Stack speichern wollen.

Objekte (deren Größe sich ändert, wenn wir sie aktualisieren) gehen auf den Haufen, weil wir zur Zeit der Erstellung nicht wissen, wie lange sie dauern werden. In vielen Sprachen wird der Heap gesammelt, um Objekte (wie das cls1-Objekt) zu finden, die keine Referenzen mehr haben.

In Java gehen die meisten Objekte direkt in den Heap. In Sprachen wie C / C ++ können Strukturen und Klassen oft auf dem Stapel bleiben, wenn Sie nicht mit Zeigern arbeiten.

Weitere Informationen finden Sie hier:

Der Unterschied zwischen Stack- und Heap-Speicherzuweisung «timmurphy.org

und hier:

Erstellen von Objekten im Stapel und Heap

Dieser Artikel ist die Quelle des obigen Bildes: Sechs wichtige .NET-Konzepte: Stack, Heap, Werttypen, Referenztypen, Boxen und Unboxing - CodeProject

aber beachten Sie, dass es einige Ungenauigkeiten enthalten kann.


350
2017-11-09 12:28



Der Stapel Wenn Sie eine Funktion aufrufen, werden die Argumente für diese Funktion und ein anderer Overhead auf den Stack gelegt. Einige Informationen (z. B. wohin Sie zurückkehren) werden dort ebenfalls gespeichert. Wenn Sie eine Variable innerhalb Ihrer Funktion deklarieren, wird diese Variable ebenfalls auf dem Stapel zugeordnet.

Die Zuweisung des Stacks ist ziemlich einfach, da Sie die Zuordnung immer in umgekehrter Reihenfolge aufheben. Stack-Stuff wird hinzugefügt, wenn Sie Funktionen eingeben, die entsprechenden Daten werden entfernt, wenn Sie sie verlassen. Das bedeutet, dass Sie dazu neigen, in einer kleinen Region des Stapels zu bleiben, wenn Sie nicht viele Funktionen aufrufen, die viele andere Funktionen aufrufen (oder eine rekursive Lösung erstellen).

Der Haufen Der Heap ist ein generischer Name für den Speicherort der Daten, die Sie im laufenden Betrieb erstellen. Wenn Sie nicht wissen, wie viele Raumschiffe Ihr Programm erstellen wird, verwenden Sie wahrscheinlich den neuen (oder malloc oder gleichwertigen) Operator, um jedes Raumschiff zu erstellen. Diese Zuweisung wird für eine Weile bestehen bleiben, also werden wir wahrscheinlich die Dinge in einer anderen Reihenfolge freigeben, als wir sie erstellt haben.

Daher ist der Heap viel komplexer, da es sich um Speicherbereiche handelt, die ungenutzt sind und mit Chunks verschachtelt sind, die - Speicher fragmentiert werden. Es ist ein schwieriges Problem, die benötigte Speicherkapazität zu finden. Aus diesem Grund sollte der Heap vermieden werden (obwohl er immer noch häufig verwendet wird).

Implementierung Die Implementierung sowohl des Stacks als auch des Heaps erfolgt normalerweise bis zur Laufzeit / dem Betriebssystem. Häufig erstellen Spiele und andere Anwendungen, die leistungskritisch sind, ihre eigenen Speicherlösungen, die einen großen Teil des Speichers vom Heap abgreifen und dann intern verteilen, um sich nicht auf das Betriebssystem als Speicher zu verlassen.

Dies ist nur praktisch, wenn die Speicherbelegung sich stark von der Norm unterscheidet - zum Beispiel für Spiele, bei denen man ein Level in einer riesigen Operation lädt und das Ganze in einer anderen riesigen Operation verschwinden lässt.

Physischer Speicherort im Speicher Dies ist weniger relevant, als Sie aufgrund einer so genannten Technologie denken Virtueller Speicher Das macht Ihr Programm zu denken, dass Sie Zugriff auf eine bestimmte Adresse haben, wo die physischen Daten woanders sind (sogar auf der Festplatte!). Die Adressen, die Sie für den Stack erhalten, sind in aufsteigender Reihenfolge, wenn Ihr Call-Tree tiefer wird. Die Adressen für den Heap sind nicht vorhersagbar (d.h. spezifisch für die Implentierung) und ehrlich gesagt nicht wichtig.


190
2017-09-17 04:27



Um klarzustellen, diese Antwort hat falsche Informationen (Thomas reparierte seine Antwort nach Kommentaren, cool :)). Andere Antworten vermeiden einfach zu erklären, was statische Zuweisung bedeutet. Also werde ich die drei Hauptformen der Zuweisung und wie sie sich normalerweise auf das Heap-, Stack- und Datensegment unten beziehen, erklären. Ich werde auch einige Beispiele in C / C ++ und Python zeigen, um den Leuten zu helfen.

"Statische" (AKA statisch zugewiesene) Variablen sind nicht auf dem Stapel zugeordnet. Gehen Sie nicht so vor - viele Leute machen nur, weil "statisch" viel wie "Stapel" klingt. Sie existieren weder im Stapel noch im Haufen. Das sind Teil von dem, was man das nennt Datensegment.

Es ist jedoch im Allgemeinen besser zu überlegen "Umfang" und "Lebenszeit"anstatt" stack "und" haufen ".

Umfang bezieht sich darauf, welche Teile des Codes auf eine Variable zugreifen können. Generell denken wir darüber nach lokaler Bereich (kann nur von der aktuellen Funktion zugegriffen werden) versus globaler Geltungsbereich (kann überall zugegriffen werden), obwohl der Umfang sehr viel komplexer werden kann.

Die Lebensdauer bezieht sich auf die Zuweisung und Deallokation einer Variablen während der Programmausführung. Normalerweise denken wir darüber nach statische Zuordnung (Die Variable bleibt während der gesamten Dauer des Programms bestehen, so dass sie für die Speicherung derselben Informationen über mehrere Funktionsaufrufe hinweg nützlich ist) automatische Zuordnung (Die Variable bleibt nur während eines einzelnen Aufrufs einer Funktion bestehen, wodurch sie nützlich ist, um Informationen zu speichern, die nur während Ihrer Funktion verwendet werden und verworfen werden können, wenn Sie fertig sind) dynamische Zuordnung (Variablen, deren Dauer zur Laufzeit definiert wird, anstatt wie statisch oder automatisch zu kompilieren).

Obwohl die meisten Compiler und Interpreter dieses Verhalten ähnlich hinsichtlich der Verwendung von Stapeln, Heaps usw. implementieren, kann ein Compiler diese Konventionen manchmal brechen, wenn er so lange wie das Verhalten richtig ist. Zum Beispiel kann aufgrund einer Optimierung eine lokale Variable nur in einem Register existieren oder vollständig entfernt werden, obwohl die meisten lokalen Variablen in dem Stapel existieren. Wie bereits in einigen Kommentaren erwähnt wurde, steht es Ihnen frei, einen Compiler zu implementieren, der nicht einmal einen Stack oder einen Heap verwendet, sondern einige andere Speichermechanismen (selten gemacht, da Stacks und Heaps dafür ideal sind).

Ich werde einen einfachen annotierten C-Code zur Verfügung stellen, um all dies zu illustrieren. Der beste Weg zu lernen ist, ein Programm unter einem Debugger auszuführen und das Verhalten zu beobachten. Wenn Sie Python lesen möchten, springen Sie zum Ende der Antwort :)

// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be "leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

Ein besonders prägnantes Beispiel dafür, warum es wichtig ist, zwischen Lebensdauer und Gültigkeitsbereich zu unterscheiden, ist, dass eine Variable einen lokalen Bereich, aber eine statische Lebensdauer haben kann, z. B. "someLocalStaticVariable" im obigen Codebeispiel. Solche Variablen können unsere gemeinsamen, aber informellen Benennungsgewohnheiten sehr verwirrend machen. Zum Beispiel wenn wir sagen "lokal"Normalerweise meinen wir"Local Scope automatisch zugewiesene Variable"Und wenn wir global sagen, meinen wir gewöhnlich"global skalierte statisch zugewiesene Variable". Leider, wenn es um Dinge wie geht"Dateibereich statisch zugewiesene Variablen"Viele Leute sagen nur ..."nicht wahr?".

Einige der Syntax-Auswahlen in C / C ++ verschärfen dieses Problem - zum Beispiel denken viele Leute, dass globale Variablen aufgrund der unten gezeigten Syntax nicht "statisch" sind.

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

Beachten Sie, dass das Setzen des Schlüsselworts "static" in der obigen Deklaration verhindert, dass var2 einen globalen Gültigkeitsbereich hat. Dennoch hat das globale var1 eine statische Zuweisung. Das ist nicht intuitiv! Aus diesem Grund versuche ich, das Wort "statisch" niemals zu verwenden, wenn ich den Umfang beschreibe, und stattdessen etwas wie "Datei" oder "Datei beschränkt" sagen. Viele Leute verwenden jedoch den Ausdruck "statisch" oder "statischer Gültigkeitsbereich", um eine Variable zu beschreiben, auf die nur aus einer Codedatei zugegriffen werden kann. Im Kontext des Lebens, "statisch" immer bedeutet, dass die Variable beim Programmstart zugewiesen und bei Programmende freigegeben wird.

Manche Leute halten diese Konzepte für C / C ++ -spezifisch. Sie sind nicht. Zum Beispiel veranschaulicht das unten stehende Python-Beispiel alle drei Arten der Zuweisung (es gibt einige feine Unterschiede, die in interpretierten Sprachen möglich sind, auf die ich hier nicht eingehen werde).

from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __name__ == "__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones

168
2017-09-17 04:48



Andere haben die weiten Striche ziemlich gut beantwortet, also werde ich ein paar Details einwerfen.

  1. Stack und Heap müssen nicht singulär sein. Eine häufige Situation, in der Sie mehr als einen Stapel haben, ist, wenn Sie mehr als einen Thread in einem Prozess haben. In diesem Fall hat jeder Thread seinen eigenen Stack. Sie können auch mehr als einen Heap haben, zum Beispiel können einige DLL-Konfigurationen dazu führen, dass verschiedene DLLs aus verschiedenen Heaps zuweisen, weshalb es im Allgemeinen eine schlechte Idee ist, Speicher freizugeben, der von einer anderen Bibliothek zugewiesen wurde.

  2. In C können Sie den Nutzen der variablen Längenzuordnung durch Verwendung von nutzen Zuteilung, die auf dem Stapel im Gegensatz zu Alloc allokiert, die auf dem Heap reserviert. Dieser Speicher wird Ihre Return-Anweisung nicht überleben, ist aber für einen Scratch-Puffer nützlich.

  3. Das Erstellen eines großen temporären Puffers unter Windows, von dem Sie nicht viel verwenden, ist nicht kostenlos. Dies liegt daran, dass der Compiler eine Stack-Probe-Schleife generiert, die bei jeder Eingabe der Funktion aufgerufen wird, um sicherzustellen, dass der Stack vorhanden ist (da Windows eine einzelne Guard-Seite am Ende des Stapels verwendet, um zu erkennen, wann der Stack wachsen muss). Wenn Sie auf mehr als eine Seite des Stacks zugreifen, stürzen Sie ab. Beispiel:

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}

155
2017-09-17 07:16



Andere haben Ihre Frage direkt beantwortet, aber wenn ich versuche, den Stack und den Heap zu verstehen, halte ich es für hilfreich, das Speicherlayout eines herkömmlichen UNIX-Prozesses (ohne Threads und mmap()Allokatoren). Das Speicherverwaltung Glossar Webseite hat ein Diagramm dieses Speicherlayouts.

Der Stapel und der Heap befinden sich traditionell an entgegengesetzten Enden des virtuellen Adressraums des Prozesses. Der Stack wächst automatisch beim Zugriff bis zu einer Größe, die vom Kernel eingestellt wird (einstellbar mit setrlimit(RLIMIT_STACK, ...)). Der Heap wächst, wenn der Speicherzuordner den Befehl aufruft brk() oder sbrk() Systemaufruf, der mehr Seiten des physischen Speichers in den virtuellen Adressraum des Prozesses abbildet.

In Systemen ohne virtuellen Speicher, wie z. B. einigen eingebetteten Systemen, gilt oft das gleiche Grundlayout, außer dass der Stack und der Heap in der Größe festgelegt sind. In anderen eingebetteten Systemen (z. B. solchen, die auf Microchip-PIC-Mikrocontrollern basieren) ist der Programmstapel jedoch ein separater Speicherblock, der nicht durch Datenbewegungsanweisungen adressierbar ist und nur indirekt durch Programmablaufanweisungen geändert oder gelesen werden kann (Aufruf, Rückkehr usw.). Andere Architekturen, wie Intel Itanium-Prozessoren, haben mehrere Stapel. In diesem Sinne ist der Stack ein Element der CPU-Architektur.


126
2017-09-17 04:57



Ich denke, viele andere Leute haben Ihnen in dieser Sache meist richtige Antworten gegeben.

Ein Detail, das jedoch übersehen wurde, ist, dass der "Haufen" eigentlich als "freier Speicher" bezeichnet werden sollte. Der Grund für diese Unterscheidung ist, dass der ursprüngliche freie Speicher mit einer Datenstruktur implementiert wurde, die als "Binomial-Heap" bekannt ist. Aus diesem Grund wurde die Zuteilung aus frühen Implementierungen von malloc () / free () von einem Heap zugewiesen. In der heutigen Zeit werden die meisten Free Stores jedoch mit sehr aufwendigen Datenstrukturen implementiert, die keine Binomialhaufen sind.


107
2017-09-17 04:29