it-swarm-eu.dev

Was und wo sind Stapel und Haufen?

Bücher über Programmiersprachen erklären, dass Werttypen auf dem stack erstellt werden und Referenztypen auf dem heap erstellt werden, ohne dass diese beiden Dinge erläutert werden. Ich habe keine klare Erklärung dazu gelesen. Ich verstehe was ein Stapel ist. Aber, 

  • wo und was sind sie (physisch im Speicher eines echten Computers)?
  • Inwiefern werden sie von der Betriebssystem- oder Sprachlaufzeit gesteuert?
  • Welchen Umfang haben sie?
  • Was bestimmt die Größe von jedem von ihnen?
  • Was macht einen schneller? 
7473
mattshane

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

Der Heap ist ein Speicher für dynamische Zuordnung. Im Gegensatz zum Stack gibt es kein erzwungenes Muster für die Zuweisung und Freigabe von Blöcken aus dem Heap. Sie können jederzeit einen Block zuordnen und jederzeit freigeben. Dies macht es viel komplexer zu verfolgen, welche Teile des Heaps zu einem bestimmten Zeitpunkt zugewiesen oder frei sind. Es stehen viele benutzerdefinierte Heap-Allokatoren zur Verfügung, um die Heap-Leistung für verschiedene Verwendungsmuster anzupassen.

Jeder Thread erhält einen Stapel, während für die Anwendung normalerweise nur ein Heap vorhanden ist (obwohl es nicht ungewöhnlich ist, dass mehrere Heaps für verschiedene Zuweisungstypen vorhanden sind).

Um Ihre Fragen direkt zu beantworten: 

Inwieweit 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. Normalerweise wird das Betriebssystem von der Sprachlaufzeit aufgerufen, um den Heapspeicher für die Anwendung zuzuweisen.

Welchen Umfang haben sie?

Der Stapel ist an einen Thread angehängt. Wenn der Thread beendet wird, wird der Stapel wieder freigegeben. Der Heapspeicher wird normalerweise beim Start der Anwendung durch die Laufzeit zugewiesen und wird zurückgefordert, wenn die Anwendung (technisch verarbeitet) 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 mit zunehmendem Speicherplatz zunehmen (der Zuweiser fordert mehr Speicher vom Betriebssystem an).

Was macht einen schneller?

Der Stack ist schneller, da das Zugriffsmuster die Zuordnung und Freigabe von Speicher für ihn einfach macht (ein Zeiger/eine ganze Zahl wird einfach inkrementiert oder dekrementiert), während der Heap viel komplexere Buchhaltung mit einer Zuweisung oder Aufhebung einer Zuordnung hat. Außerdem wird jedes Byte im Stack sehr häufig wiederverwendet, was bedeutet, dass es dem Cache des Prozessors zugeordnet wird, wodurch es sehr schnell wird. Ein weiterer Leistungstreffer für den Heap ist, dass der Heap, der meist eine globale Ressource ist, in der Regel Multithreading-sicher sein muss, d.

Eine klare Demonstration:
Bildquelle: vikashazrati.wordpress.com

5529
Jeff Hill

Stapel:

  • Gespeichert in Computer RAM genauso wie der Heap.
  • Auf dem Stack erstellte Variablen werden aus dem Gültigkeitsbereich entfernt und automatisch aufgehoben.
  • Im Vergleich zu Variablen auf dem Heap viel schneller zuzuordnen.
  • Implementiert mit einer tatsächlichen Stack-Datenstruktur.
  • Speichert lokale Daten, Rücksprungadressen, die für die Parameterübergabe verwendet werden.
  • Kann zu einem Stapelüberlauf führen, wenn zu viel Stapel verwendet wird (meistens bei unendlicher oder zu tiefer Rekursion, sehr große Zuweisungen).
  • Auf dem Stack erstellte Daten können ohne Zeiger verwendet werden.
  • Sie würden den Stack verwenden, wenn Sie genau wissen, wie viele Daten Sie vor der Kompilierzeit zuordnen müssen und dass diese nicht zu groß sind.
  • Normalerweise ist bereits eine maximale Größe festgelegt, wenn Ihr Programm startet.

Haufen:

  • Gespeichert im Computer RAM wie beim Stack.
  • In C++ müssen Variablen auf dem Heap manuell gelöscht werden und fallen niemals aus dem Bereich. Die Daten werden mit delete, delete[] oder free freigegeben.
  • Im Vergleich zu Variablen auf dem Stack langsamer zuzuordnen.
  • Wird bei Bedarf verwendet, um einen Datenblock für das Programm zuzuweisen.
  • Kann eine Fragmentierung aufweisen, wenn viele Zuordnungen und Freigabeverpflichtungen vorliegen.
  • In C++ oder C werden Daten, die auf dem Heap erstellt werden, durch Zeiger angezeigt und mit new bzw. malloc zugewiesen.
  • Kann Zuweisungsfehler haben, wenn ein zu großer Puffer zur Zuordnung angefordert wird.
  • Sie würden den Heapspeicher verwenden, wenn Sie nicht genau wissen, wie viele Daten Sie zur Laufzeit benötigen, oder wenn Sie viele Daten zuordnen 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;
2199
Brian R. Bondy

Der wichtigste Punkt ist, dass Heap und Stack allgemeine Begriffe für die Zuordnung 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 abgelegt wurden, übereinander, und Sie können nur den obersten Gegenstand entfernen (ohne das Ganze zu kippen).

    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 einzigen Statusinformationen, die Sie benötigen, sind ein einzelner Zeiger auf das Ende des Stapels. Um zuzuordnen und die Zuweisung aufzuheben, erhöhen und verringern Sie diesen einzelnen Zeiger. Hinweis: Ein Stack kann manchmal so implementiert werden, dass er am oberen Rand eines Speicherbereichs beginnt und sich nach unten erstreckt, anstatt nach oben zu wachsen.

  • In einem Haufen gibt es keine bestimmte Reihenfolge für die Platzierung von Elementen. Sie können Artikel in beliebiger Reihenfolge erreichen und entfernen, da es keinen eindeutigen Top-Artikel gibt.

    Heap like a heap of licorice allsorts

    Für die Heap-Zuweisung ist es erforderlich, eine vollständige Aufzeichnung des zugewiesenen Speichers und des nicht vorhandenen Speicherplatzes sowie einige zusätzliche Verwaltungsaufgaben zur Verringerung der Fragmentierung zu führen, um zusammenhängende Speichersegmente zu finden, die groß genug sind, um die angeforderte Größe anzupassen, und so weiter. Speicher kann jederzeit freigegeben werden, wobei freier Speicherplatz zur Verfügung steht. Manchmal führt ein Speicherzuweiser Wartungsaufgaben aus, z. B. das Defragmentieren des Speichers, indem der zugewiesene Speicher verschoben wird, oder die Speicherbereinigung - zur Laufzeit erkannt, wenn sich der Speicher nicht mehr im Speicher befindet, und die Zuweisung aufheben. 

Diese Bilder sollten die beiden Möglichkeiten zum Zuweisen und Freigeben von Speicher in einem Stapel und einem Heap ziemlich gut beschreiben. Lecker

  • Inwiefern werden sie von der Betriebssystem- oder Sprachlaufzeit gesteuert?

    Wie erwähnt, sind Heap und Stack allgemeine Begriffe, die auf viele Arten implementiert werden können. Computerprogramme haben in der Regel einen Stack namens call stack , in dem Informationen gespeichert werden, die für die aktuelle Funktion relevant sind, z. B. einen Zeiger auf die Funktion, von der sie aufgerufen wurden, und lokale Variablen. Da Funktionen andere Funktionen aufrufen und dann zurückkehren, wächst und verkleinert sich der Stack, um Informationen aus den Funktionen im Call Stack zu speichern. Ein Programm hat nicht wirklich die Kontrolle über die Laufzeit. Es wird durch die Programmiersprache, das Betriebssystem und sogar die Systemarchitektur bestimmt.

    Ein Heap ist ein allgemeiner Begriff für jeden Speicher, der dynamisch und zufällig zugewiesen wird. nicht in Ordnung. Der Speicher wird normalerweise vom Betriebssystem zugewiesen, wobei die Anwendung API-Funktionen aufruft, um diese Zuordnung durchzuführen. Bei der Verwaltung von dynamisch zugewiesenem Speicher, der normalerweise vom Betriebssystem verwaltet wird, ist ein gewisser Aufwand erforderlich.

  • Welchen Umfang haben sie?

    Der Aufrufstapel ist ein so niedriges Konzept, dass er sich nicht auf "Umfang" im Sinne der Programmierung bezieht. Wenn Sie Code zerlegen, werden relative Zeigerstilverweise auf Teile des Stapels angezeigt. Wenn es sich jedoch um eine Sprache auf höherer Ebene handelt, gelten für die Sprache eigene Gültigkeitsregeln. Ein wichtiger Aspekt eines Stapels ist jedoch, dass, wenn eine Funktion zurückkehrt, alles, was zu dieser Funktion lokal ist, sofort vom Stapel freigegeben wird. Das funktioniert so, wie Sie es von Ihrer Programmiersprache erwarten würden. In einem Haufen ist es auch schwer zu definieren. Der Geltungsbereich ist derjenige, der vom Betriebssystem bereitgestellt wird, aber Ihre Programmiersprache fügt wahrscheinlich Regeln hinzu, was ein "Geltungsbereich" in Ihrer Anwendung ist. Die Prozessorarchitektur und das Betriebssystem verwenden eine virtuelle Adressierung, die der Prozessor in physische Adressen umwandelt, und es gibt Seitenfehler usw. Sie verfolgen, welche Seiten zu welchen Anwendungen gehören. Sie müssen sich jedoch nie wirklich darüber Sorgen machen, da Sie einfach die Methode verwenden, die Ihre Programmiersprache verwendet, um Speicher zuzuweisen und freizugeben, und nach Fehlern suchen (wenn die Zuweisung/Freigabe aus irgendeinem Grund fehlschlägt).

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

    Dies hängt wiederum von der Sprache, dem Compiler, dem Betriebssystem und der Architektur ab. Ein Stack ist in der Regel vorbelegt, da er per Definition zusammenhängenden Speicher haben muss (mehr dazu im letzten Absatz). Der Sprachcompiler oder das Betriebssystem bestimmen seine Größe. Sie speichern keine großen Datenblöcke auf dem Stack. Daher sind sie so groß, dass sie niemals vollständig verwendet werden sollten, es sei denn, es kommt zu einer unerwünschten endlosen Rekursion (daher "Stack-Overflow") oder zu 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 sowieso sehr abstrahiert, so dass Sie sich normalerweise keine Gedanken darüber machen müssen, wie es funktioniert, außer dass Sie (in Sprachen, in denen Sie dies zulassen) keinen Speicher verwenden dürfen Sie haben noch keinen Speicherplatz zugewiesen, den Sie freigegeben haben.Was macht einen schneller?.

  • What makes one faster?

    The stack is faster because all free memory is always contiguous. No list needs to be maintained of all the segments of free memory, just a single pointer to the current top of the stack. Compilers usually store this pointer in a special, fast register for this purpose. What's more, subsequent operations on a stack are usually concentrated within very nearby areas of memory, which at a very low level is good for optimization by the processor on-die caches.

1305
thomasrutter

(Ich habe diese Antwort aus einer anderen Frage gestellt, die mehr oder weniger ein Betrogener dieser Frage war.)

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 verfügt jeder Thread über einen eigenen, völlig unabhängigen Stack, der jedoch den Heap gemeinsam nutzt. 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 der verwendeten und freien Blöcke. Neue Zuordnungen auf dem Heap (durch new oder malloc) werden erfüllt, indem aus einem der freien Blöcke ein geeigneter Block erstellt wird. Dies erfordert die Aktualisierung der Liste der Blöcke auf dem Heap. Diese Metainformation über die Blöcke auf dem Heap wird auch auf dem Heap gespeichert, oft in einem kleinen Bereich direkt vor jedem Block.
  • Wenn der Heap wächst, werden neue Blöcke häufig von niedrigeren Adressen zu höheren Adressen zugewiesen. So können Sie sich den Heap als Heap von Speicherblöcken vorstellen, dessen Größe mit der Zuweisung von Speicher zunimmt. Wenn der Heapspeicher für eine Zuordnung zu klein ist, kann die Größe häufig erhöht werden, indem mehr Speicher vom zugrunde liegenden Betriebssystem erworben wird.
  • Durch die Zuweisung und Freigabe vieler kleiner Blöcke wird der Heap möglicherweise in einem Zustand belassen, in dem sich viele kleine freie Blöcke zwischen den verwendeten Blöcken befinden. Eine Anforderung zum Zuweisen eines großen Blocks kann fehlschlagen, da 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. Dies wird als Heap-Fragmentierung bezeichnet.
  • Wenn ein benutzter Block, der einem freien Block benachbart ist, freigegeben wird, kann der neue freie Block mit dem benachbarten freien Block zusammengefügt werden, um einen größeren freien Block zu erzeugen, der die Fragmentierung des Heap effektiv verringert.

The heap

Der Stapel

  • Der Stack arbeitet oft eng mit einem speziellen Register in der CPU mit dem Namen Stack-Pointer zusammen. Der Stapelzeiger zeigt anfangs auf den Stapel (die höchste Adresse im Stapel).
  • Die CPU verfügt über spezielle Anweisungen für Push-Werte auf den Stack und Popping diese vom Stack zurück. Jedes Push 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 (seien Sie nicht verwirrt durch die Tatsache, dass Hinzufügen einen Wert für den Stapel verringert der Stapelzeiger und entfernen einen Wert erhöht it. 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 aktuellen Befehlszeiger drücken, d. H. Die Adresse des Codes, der auf dem Stapel ausgeführt wird. Die CPU springt dann zu der Funktion, indem der -Anweisungszeiger auf die Adresse der aufgerufenen Funktion gesetzt wird. Später, wenn die Funktion zurückkehrt, wird der alte Befehlszeiger aus dem Stapel geholt und die Ausführung am Code unmittelbar nach dem Aufruf der Funktion fortgesetzt.
  • Wenn eine Funktion eingegeben wird, wird der Stapelzeiger verkleinert, um mehr Platz auf dem Stapel für lokale (automatische) Variablen zu reservieren. Wenn die Funktion eine lokale 32-Bit-Variable hat, werden vier Bytes auf dem Stapel reserviert. Wenn die Funktion zurückkehrt, wird der Stapelzeiger zurückbewegt, um den zugewiesenen Bereich freizugeben.
  • Wenn eine Funktion über Parameter verfügt, werden diese vor dem Aufruf der Funktion auf den Stack verschoben. Der Code in der Funktion kann dann vom Stapelzeiger auf den Stack navigieren, um diese Werte zu finden.
  • Verschachtelungsfunktionen funktionieren wie ein Zauber. Bei jedem neuen Aufruf werden Funktionsparameter, die Rücksprungadresse und der Speicherplatz für lokale Variablen zugewiesen. Diese Aktivierungsdatensätze können für verschachtelte Aufrufe gestapelt werden und werden bei der Rückkehr der Funktionen in der richtigen Weise abgewickelt.
  • Da der Stack ein begrenzter Speicherblock ist, können Sie ein Stack Overflow verursachen, indem Sie zu viele verschachtelte Funktionen aufrufen und/oder zu viel Speicherplatz für lokale Variablen zuweisen. Häufig ist der für den Stapel verwendete Speicherbereich so eingerichtet, dass das Schreiben unter der untersten (niedrigsten Adresse) des Stapels einen Trap oder eine Ausnahme in der CPU auslöst. Diese Ausnahmebedingung kann dann von der Laufzeitumgebung abgefangen und in eine Art Stapelüberlaufausnahme konvertiert werden.

The stack

Kann eine Funktion anstelle eines Stapels auf dem Heap zugewiesen werden?

Nein, Aktivierungsdatensätze für Funktionen (d. H. Lokale oder automatische Variablen) werden auf dem Stack 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 verwendet malloc und C++ verwendet new, aber viele andere Sprachen verfügen über eine Garbage Collection.Bei dem Stack handelt es sich jedoch um ein eher untergeordnetes Feature, das eng mit der Prozessorarchitektur verbunden ist. Das Aufwachsen des Heaps, wenn nicht genügend Speicherplatz vorhanden ist, ist nicht zu schwer, da es in dem Bibliotheksaufruf implementiert werden kann, der den Heap verarbeitet. Das Wachsen des Stacks ist jedoch oft unmöglich, da der Stapelüberlauf nur erkannt wird, wenn er zu spät ist. und das Herunterfahren des Threads der Ausführung ist die einzig gangbare Option.

However, the stack is a more low-level feature closely tied to the processor architecture. Growing the heap when there is not enough space isn't too hard since it can be implemented in the library call that handles the heap. However, growing the stack is often impossible as the stack overflow only is discovered when it is too late; and shutting down the thread of execution is the only viable option.

685

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 muss nur so lange dauern, wie der Funktionsaufruf in den Stack geht. Der Heap wird für Variablen verwendet, deren Lebensdauer wir im Vorfeld nicht wirklich kennen. In den meisten Sprachen ist es wichtig, dass wir zur Kompilierzeit wissen, wie groß eine Variable ist, wenn wir sie auf dem Stack speichern möchten. 

Objekte (deren Größe sich bei der Aktualisierung ändert) werden auf dem Heap gespeichert, da wir zum Zeitpunkt der Erstellung nicht wissen, wie lange sie dauern werden. In vielen Sprachen wird der Haufen Müll gesammelt, um Objekte zu finden (z. B. das Objekt cls1), die keine Referenzen mehr enthalten. 

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

Weitere Informationen finden Sie hier:

Der Unterschied zwischen Stapel- und Heapspeicherzuordnung «timmurphy.org

und hier: 

Objekte auf dem Stack und Heap erstellen

Dieser Artikel enthält die Quelle des obigen Bildes: Sechs wichtige .NET-Konzepte: Stack, Heap, Wertetypen, Referenztypen, Boxen und Unboxing - CodeProject

seien Sie sich jedoch bewusst, dass sie Ungenauigkeiten enthalten kann. 

379
Snowcrash

The Stack Wenn Sie eine Funktion aufrufen, werden die Argumente für diese Funktion plus ein anderer Overhead auf den Stapel gelegt. Dort werden auch einige Informationen gespeichert (z. B. wohin mit der Rückgabe). Wenn Sie eine Variable innerhalb Ihrer Funktion deklarieren, wird diese Variable auch auf dem Stack zugewiesen. 

Das Aufheben der Stapelzuweisung ist ziemlich einfach, da Sie die Aufhebung der Zuweisung immer in umgekehrter Reihenfolge vornehmen. Stack-Sachen werden hinzugefügt, wenn Sie Funktionen eingeben, die entsprechenden Daten werden entfernt, wenn Sie sie verlassen. Das bedeutet, dass Sie sich normalerweise in einem kleinen Bereich des Stapels aufhalten, es sei denn, Sie rufen viele Funktionen auf, die viele andere Funktionen aufrufen (oder eine rekursive Lösung erstellen).

The Heap Der Heap ist ein generischer Name, an den Sie die Daten legen, die Sie erstellen. Wenn Sie nicht wissen, wie viele Raumschiffe Ihr Programm erstellen wird, verwenden Sie wahrscheinlich den neuen Operator (oder Malloc oder einen gleichwertigen Operator), um jedes Raumschiff zu erstellen. Diese Zuteilung wird noch eine Weile erhalten bleiben, daher werden wir wahrscheinlich Dinge in einer anderen Reihenfolge freigeben als wir sie erstellt haben. 

Daher ist der Heap viel komplexer, da sich am Ende Speicherbereiche befinden, die ungenutzt mit verschachtelten Blöcken verschachtelt sind - Speicher wird fragmentiert. Das Finden eines freien Speichers in der von Ihnen benötigten Größe ist ein schwieriges Problem. Aus diesem Grund sollte der Heap vermieden werden (obwohl er noch häufig verwendet wird).

Implementierung Die Implementierung von Stack und Heap hängt in der Regel von der Laufzeit/OS ab. Häufig erstellen Spiele und andere Anwendungen, die für die Leistung von Bedeutung sind, eigene Speicherlösungen, die einen großen Teil des Speichers vom Heap nehmen und dann intern auslagern, um zu vermeiden, dass das Betriebssystem für den Speicher verwendet wird. 

Dies ist nur praktisch, wenn sich die Speichernutzung erheblich von der Norm unterscheidet - d. H. Für Spiele, bei denen Sie ein Level in einem einzigen riesigen Vorgang laden und den gesamten Speicherplatz in einem anderen großen Vorgang ablegen können.

Physikalischer Speicherort Dies ist weniger relevant als Sie denken, da die Technologie Virtual Memory genannt wird, wodurch Ihr Programm den Eindruck hat, dass Sie Zugriff auf eine bestimmte Adresse haben, an der sich die physischen Daten befinden ( sogar auf der Festplatte!). Die Adressen, die Sie für den Stack erhalten, werden in zunehmender Reihenfolge angezeigt, wenn der Aufrufbaum tiefer wird. Die Adressen für den Heap sind nicht vorhersagbar (d. H. Implimentationsspezifisch) und offen gesagt nicht wichtig.

194
Tom Leys

Um zu klären, diese Antwort hat falsche Informationen ( thomas hat seine Antwort nach Kommentaren korrigiert, cool :)). Andere Antworten vermeiden es zu erklären, was statische Zuordnung bedeutet. Ich werde daher die drei Hauptformen der Zuordnung erklären und wie sie sich normalerweise auf den Heap-, Stack- und Datensegment beziehen. Ich werde auch einige Beispiele in C/C++ und Python zeigen, um den Leuten das Verständnis zu erleichtern.

"Statische" (AKA-statisch zugeordnete) Variablen werden auf dem Stack nicht zugewiesen. Gehen Sie nicht davon aus - viele Leute tun dies nur, weil "statisch" sehr nach "stack" klingt. Sie sind weder im Stapel noch im Haufen vorhanden. Sie sind Teil des sogenannten Datensegments .

Im Allgemeinen ist es jedoch besser, "scope" und "lifetime" als "stack" und "heap" zu betrachten.

Bereich bezieht sich auf die Teile des Codes, die auf eine Variable zugreifen können. Im Allgemeinen denken wir an local scope (kann nur von der aktuellen Funktion aufgerufen werden) gegenüber globaler scope (kann von überall aus aufgerufen werden), obwohl der Umfang sehr viel komplexer werden kann.

Die Lebensdauer bezieht sich darauf, wann eine Variable während der Programmausführung zugewiesen und freigegeben wird. Normalerweise denken wir an static Allocation (die Variable bleibt während der gesamten Dauer des Programms erhalten, wodurch sie die gleichen Informationen für mehrere Funktionsaufrufe speichert) gegenüber Automatic Allocation (Variable bleibt nur während eines einzelnen Aufrufs erhalten.) zu einer Funktion, wodurch es nützlich ist, Informationen zu speichern, die nur während Ihrer Funktion verwendet werden und verworfen werden können, wenn Sie fertig sind) oder Dynamic Allocation (Variablen, deren Dauer zur Laufzeit definiert ist, anstelle der statischen oder automatischen Kompilierzeit) ).

Obwohl die meisten Compiler und Interpreter dieses Verhalten in Bezug auf die Verwendung von Stapeln, Heaps usw. auf ähnliche Weise implementieren, kann ein Compiler diese Konventionen manchmal brechen, wenn er will, solange das Verhalten korrekt ist. Beispielsweise kann eine lokale Variable aufgrund von Optimierung nur in einem Register vorhanden sein oder vollständig entfernt werden, obwohl die meisten lokalen Variablen im Stapel vorhanden sind. Wie in einigen Kommentaren darauf hingewiesen 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 hervorragend geeignet sind).

Ich werde einen einfachen kommentierten C-Code bereitstellen, um all dies zu veranschaulichen. Der beste Weg zu lernen ist, ein Programm unter einem Debugger auszuführen und das Verhalten zu beobachten. Wenn Sie lieber Python lesen möchten, fahren Sie mit dem Ende der Antwort fort :)

// 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 anschauliches Beispiel dafür, warum es wichtig ist, zwischen der Lebensdauer und dem Gültigkeitsbereich zu unterscheiden, ist, dass eine Variable einen lokalen Gültigkeitsbereich, aber eine statische Lebensdauer haben kann - beispielsweise "someLocalStaticVariable" im obigen Codebeispiel. Solche Variablen können unsere gemeinsamen, aber informellen Benennungsgewohnheiten sehr verwirrend machen. Wenn wir zum Beispiel "local" sagen, meinen wir normalerweise "automatisch zugeordnete Variable mit lokalem Bereich" und wenn wir global sagen, meinen wir normalerweise "statisch zugewiesene Variable mit globalem Bereich ". Leider, wenn es um Dinge wie "Datei-Bereich statisch zugewiesene Variablen" geht, sagen viele Leute ... "huh ???".

Einige der Syntaxauswahlmöglichkeiten in C/C++ verstärken dieses Problem. Beispielsweise glauben viele Leute, dass globale Variablen aufgrund der im Folgenden 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 die Verwendung des Schlüsselworts "static" in der obigen Deklaration den globalen Gültigkeitsbereich von var2 verhindert. Trotzdem hat das globale var1 eine statische Zuordnung. Das ist nicht intuitiv! Aus diesem Grund versuche ich niemals, das Wort "statisch" bei der Beschreibung des Bereichs zu verwenden, und sage stattdessen so etwas wie "Datei" oder "Dateibeschränkt". Viele verwenden jedoch den Ausdruck "statisch" oder "statischer Gültigkeitsbereich", um eine Variable zu beschreiben, auf die nur von einer Codedatei aus zugegriffen werden kann. Im Kontext der Lebensdauer bedeutet "statisch" immer, dass die Variable beim Programmstart zugewiesen und freigegeben wird, wenn das Programm beendet wird.

Einige Leute halten diese Konzepte für C/C++ spezifisch. Sie sind nicht. Das folgende Python-Beispiel veranschaulicht beispielsweise alle drei Arten der Zuordnung (in interpretierten Sprachen sind einige Unterschiede möglich, auf die ich hier nicht näher eingehen möchte).

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 __== "__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
174
davec

Andere haben die großen Schläge ziemlich gut beantwortet, also werde ich ein paar Details einbringen.

  1. Stack und Heap müssen nicht einzigartig 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 einen eigenen Stapel. Sie können auch mehr als einen Heap haben. Beispielsweise können einige DLL -Konfigurationen dazu führen, dass unterschiedliche DLLs von verschiedenen Heaps zugewiesen werden. Aus diesem Grund ist es im Allgemeinen eine schlechte Idee, den von einer anderen Bibliothek zugewiesenen Speicher freizugeben.

  2. In C können Sie den Vorteil der Zuweisung variabler Länge durch die Verwendung von Alloca erhalten, die auf dem Stack im Gegensatz zu Allocation, die auf dem Heap Allocation zuweist, verwendet. Dieser Speicher wird Ihre return-Anweisung nicht überleben, ist jedoch 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-Test-Schleife generiert, die bei jeder Eingabe Ihrer Funktion aufgerufen wird, um sicherzustellen, dass der Stack vorhanden ist (da Windows eine einzelne Guard-Seite am Ende des Stacks verwendet, um zu erkennen, wann der Stack vergrößert werden muss). Wenn Sie auf Speicher zugreifen, der mehr als eine Seite vom Stapelende entfernt ist, stürzen Sie ab). Beispiel:

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

Andere haben Ihre Frage direkt beantwortet, aber wenn Sie versuchen, den Stack und den Heap zu verstehen, halte ich es für hilfreich, das Speicherlayout eines herkömmlichen UNIX-Prozesses (ohne Threads und mmap()-basierte Zuordnungen) zu berücksichtigen. Die Memory Management Glossary Webseite enthält ein Diagramm dieses Speicherlayouts.

Der Stack und der Heap befinden sich traditionell an gegenüberliegenden Enden des virtuellen Adressraums des Prozesses. Der Stack wächst automatisch bei Zugriff auf eine vom Kernel festgelegte Größe (die mit setrlimit(RLIMIT_STACK, ...) eingestellt werden kann). Der Heapspeicher wird größer, wenn der Speicherzuweiser den Systemaufruf brk() oder sbrk() aufruft, wodurch mehr Seiten des physischen Speichers in den virtuellen Adressraum des Prozesses abgebildet werden. 

In Systemen ohne virtuellen Speicher, wie z. B. einigen eingebetteten Systemen, gilt häufig dasselbe grundlegende Layout, mit der Ausnahme, dass Stack und Heap in der Größe festgelegt sind. In anderen eingebetteten Systemen (z. B. auf Microchip PIC-Mikrocontrollern basierenden Systemen) ist der Programmstapel jedoch ein separater Speicherblock, der nicht durch Datenbewegungsanweisungen adressiert werden kann und nur indirekt durch Programmflussanweisungen (Aufruf Rückkehr usw.). Andere Architekturen wie Intel Itanium-Prozessoren verfügen über mehrere Stacks . In diesem Sinne ist der Stack ein Element der CPU-Architektur.

127
bk1e

Der Stack ist ein Teil des Arbeitsspeichers, der über mehrere Tastaturbefehle der Assembly-Sprache bearbeitet werden kann, z. B. 'pop' (Wert aus dem Stack entfernen und zurückgeben) und 'Push' (Push-Wert zum Stack), aber auch Aufruf ( Rufen Sie eine Subroutine auf (dies drückt die Adresse, um zum Stack zurückzukehren) und Return (Rückkehr von einer Subroutine - dies ruft die Adresse vom Stack ab und springt zu ihr). Dies ist der Speicherbereich unterhalb des Stack-Pointer-Registers, der nach Bedarf eingestellt werden kann. Der Stack wird auch dazu verwendet, Argumente an Unterprogramme zu übergeben und die Werte in Registern zu erhalten, bevor Unterprogramme aufgerufen werden.

Der Heap ist ein Teil des Speichers, der vom Betriebssystem an eine Anwendung übergeben wird, normalerweise durch einen Systemaufruf wie malloc. Bei modernen Betriebssystemen besteht dieser Speicher aus einer Reihe von Seiten, auf die nur der aufrufende Prozess Zugriff hat.

Die Größe des Stacks wird zur Laufzeit festgelegt und wächst im Allgemeinen nicht nach dem Programmstart. In einem C-Programm muss der Stack groß genug sein, um jede in jeder Funktion deklarierte Variable aufzunehmen. Der Heap wird bei Bedarf dynamisch anwachsen, aber das Betriebssystem ruft letztendlich den Aufruf auf (der Heap wird häufig um mehr als den von malloc angeforderten Wert vergrößert, so dass zumindest einige zukünftige Mallocs nicht in den Kernel zurückkehren müssen Holen Sie sich mehr Speicher. Dieses Verhalten ist oft anpassbar.

Da Sie den Stapel vor dem Start des Programms zugewiesen haben, müssen Sie nie vor dem Verwenden des Stapels einen Malloc-Befehl erstellen. In der Praxis ist es sehr schwer vorherzusagen, was schnell und was in modernen Betriebssystemen mit virtuellen Speichersubsystemen langsam ist, da die Implementierung der Seiten und der Speicherort der Seiten ein Implementierungsdetail sind. 

108
Daniel Papasian

Ich denke, viele andere Leute haben Ihnen in dieser Angelegenheit meistens richtige Antworten gegeben.

Ein Detail, das bisher übersehen wurde, ist jedoch, dass der "Haufen" wahrscheinlich als "Freispeicher" 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" bezeichnet wird. Aus diesem Grund war die Zuweisung aus frühen Implementierungen von malloc ()/free () die Zuweisung aus einem Heap. In der heutigen Zeit werden die meisten freien Speicher jedoch mit sehr ausgefeilten Datenstrukturen implementiert, bei denen es sich nicht um Binomialspeicher handelt.

107
Heath

Was ist ein Stack?

Ein Stapel ist ein Stapel von Objekten, normalerweise einer, die übersichtlich angeordnet ist.

 Enter image description here

Stapel in Computerarchitekturen sind Speicherbereiche, in denen Daten in einer Last-in-First-out-Weise hinzugefügt oder entfernt werden. 
In einer Multithread-Anwendung hat jeder Thread einen eigenen Stapel.

Was ist ein Haufen?

Ein Haufen ist eine unordentliche Ansammlung von Dingen, die zufällig aufgestapelt werden.

 Enter image description here

In Computerarchitekturen ist der Heap ein Bereich mit dynamisch zugewiesenem Speicher, der automatisch vom Betriebssystem oder der Speichermanager-Bibliothek verwaltet wird. 
Der Speicher auf dem Heapspeicher wird während der Programmausführung regelmäßig zugewiesen, freigegeben und dessen Größe geändert. Dies kann zu einem Problem führen, das als Fragmentierung bezeichnet wird. 
Fragmentierung tritt auf, wenn Speicherobjekte mit kleinen Zwischenräumen zugewiesen werden, die zu klein sind, um zusätzliche Speicherobjekte aufzunehmen. 
Das Nettoergebnis ist ein Prozentsatz des Speicherplatzes, der nicht für weitere Speicherzuordnungen verwendet werden kann.

Beide zusammen

In einer Multithread-Anwendung hat jeder Thread einen eigenen Stapel. Aber alle verschiedenen Threads teilen sich den Heap. 
Da die verschiedenen Threads den Heap in einer Anwendung mit mehreren Threads gemeinsam nutzen, bedeutet dies auch, dass zwischen den Threads eine gewisse Koordination bestehen muss, damit sie nicht versuchen, auf dieselben Speicherbereiche im Heap zuzugreifen und diese zu bearbeiten die selbe Zeit.

Was ist schneller - der Stapel oder der Haufen? Und warum?

Der Stapel ist viel schneller als der Haufen. 
Dies liegt an der Art und Weise, wie Speicher auf dem Stapel reserviert wird. 
Um Speicherplatz auf dem Stapel zuzuordnen, müssen Sie lediglich den Stapelzeiger nach oben verschieben.

Für Leute, die noch nicht mit der Programmierung vertraut sind, ist es wahrscheinlich eine gute Idee, den Stack zu verwenden, da dies einfacher ist. 
Da der Stapel klein ist, sollten Sie ihn verwenden, wenn Sie genau wissen, wie viel Speicher Sie für Ihre Daten benötigen, oder wenn Sie wissen, dass die Größe Ihrer Daten sehr klein ist. 
Es ist besser, den Heapspeicher zu verwenden, wenn Sie wissen, dass Sie viel Speicherplatz für Ihre Daten benötigen, oder Sie sind einfach nicht sicher, wie viel Speicherplatz Sie benötigen (wie bei einem dynamischen Array).

Java-Speichermodell

 Enter image description here

Der Stack ist der Speicherbereich, in dem lokale Variablen (einschließlich Methodenparameter) gespeichert werden. Bei Objektvariablen handelt es sich lediglich um Verweise (Zeiger) auf die eigentlichen Objekte auf dem Heap.
Jedes Mal, wenn ein Objekt instanziiert wird, wird ein Haufen Arbeitsspeicher für die Daten (den Zustand) dieses Objekts reserviert. Da Objekte andere Objekte enthalten können, können einige dieser Daten tatsächlich Verweise auf diese verschachtelten Objekte enthalten.

101
Shreyos Adikari

Sie können einige interessante Dinge mit dem Stapel machen. Beispielsweise verfügen Sie über Funktionen wie alloca (vorausgesetzt, Sie können an den zahlreichen Warnhinweisen bezüglich seiner Verwendung vorbeikommen), bei denen es sich um eine Malloc handelt, die den Stapel und nicht den Heap für den Speicher verwendet.

Stack-basierte Speicherfehler gehören zu den schlimmsten, die ich je erlebt habe. Wenn Sie Heap-Speicher verwenden und die Grenzen Ihres zugewiesenen Blocks überschreiten, haben Sie eine gute Chance, einen Segmentfehler auszulösen. (Nicht 100%: Ihr Block kann zufällig neben einem anderen liegen, den Sie zuvor zugewiesen haben.) Da auf dem Stapel erstellte Variablen jedoch immer zusammenhängend sind, kann das Schreiben außerhalb der Grenzen den Wert einer anderen Variablen ändern. Ich habe gelernt, dass jedes Mal, wenn ich das Gefühl habe, dass mein Programm aufgehört hat, die Gesetze der Logik zu befolgen, der Pufferüberlauf wahrscheinlich ist.

87
Peter

Der Stack ist einfach der Ort, an dem lokale Variablen erstellt werden. Bei jedem Aufruf einer Subroutine werden außerdem der Programmzähler (Zeiger auf die nächste Maschinenanweisung) und alle wichtigen Register und manchmal die Parameter auf den Stapel gelegt. Dann werden alle lokalen Variablen innerhalb der Subroutine auf den Stack verschoben (und von dort verwendet). Wenn die Subroutine beendet ist, wird dieses Zeug vom Stapel zurückgeworfen. Die PC- und Registerdaten werden so gespeichert, wie sie abgelegt wurden, damit Ihr Programm seinen fröhlichen Weg gehen kann.

Der Heap ist der Speicherbereich, aus dem dynamische Speicherzuordnungen bestehen (explizite "neue" oder "zuweisende" Aufrufe). Es ist eine spezielle Datenstruktur, die Speicherblöcke unterschiedlicher Größe und ihren Zuordnungsstatus nachverfolgen kann.

In "klassischen" Systemen RAM wurde der Stapelzeiger am unteren Rand des Speichers, der Heapzeiger oben gestartet und sie wuchsen aufeinander zu. Wenn sie sich überlappen, haben Sie keinen RAM mehr. Das funktioniert jedoch nicht mit modernen Multi-Threaded-Betriebssystemen. Jeder Thread muss einen eigenen Stack haben, der dynamisch erstellt werden kann.

84
T.E.D.

Aus WikiAnwser.

Stapel

Wenn eine Funktion oder eine Methode eine andere Funktion aufruft, die wiederum eine andere Funktion usw. aufruft, bleibt die Ausführung all dieser Funktionen ausgesetzt, bis die letzte Funktion ihren Wert zurückgibt.

Diese Kette angehaltener Funktionsaufrufe ist der Stapel, da Elemente im Stapel (Funktionsaufrufe) voneinander abhängig sind.

Der Stack ist bei der Ausnahmebehandlung und Threadausführung zu berücksichtigen.

Haufen

Der Heapspeicher ist einfach der Speicher, in dem die Programme die Variablen speichern. Das Element des Heapspeichers (Variablen) hat keine Abhängigkeiten und kann jederzeit nach dem Zufallsprinzip aufgerufen werden.

78
devXen

Stapel

  • Sehr schneller Zugang
  • Müssen Sie Variablen nicht explizit aufheben
  • Der Speicherplatz wird effizient von der CPU verwaltet, der Speicher wird nicht fragmentiert
  • Nur lokale Variablen
  • Begrenzung der Stackgröße (abhängig vom Betriebssystem)
  • Variablen können nicht in der Größe geändert werden

Haufen

  • Auf Variablen kann global zugegriffen werden
  • Keine Begrenzung der Speichergröße
  • (Relativ) langsamerer Zugriff
  • Keine garantierte effiziente Nutzung des Speicherplatzes. Der Speicher kann mit der Zeit fragmentiert werden, wenn Speicherblöcke zugewiesen und dann freigegeben werden
  • Sie müssen den Speicher verwalten (Sie müssen Variablen zuweisen und freigeben).
  • Variablen können mit realloc () verkleinert werden
50
unknown

Zusamenfassend

Ein Stapel wird für die statische Speicherzuweisung und ein Heap für die dynamische Speicherzuweisung verwendet, die beide im RAM des Computers gespeichert sind.


Im Detail

Der Stapel

Der Stack ist eine "LIFO" -Datenstruktur (Last In, First Out), die von der CPU sehr genau verwaltet und optimiert wird. Jedes Mal, wenn eine Funktion eine neue Variable deklariert, wird sie auf den Stapel "geschoben". Jedes Mal, wenn eine Funktion beendet wird, werden alle von dieser Funktion auf den Stapel geschobenen Variablen freigegeben (dh gelöscht). Sobald eine Stapelvariable freigegeben ist, wird dieser Speicherbereich für andere Stapelvariablen verfügbar.

Der Vorteil der Verwendung des Stacks zum Speichern von Variablen besteht darin, dass der Speicher für Sie verwaltet wird. Sie müssen den Speicher nicht manuell zuweisen oder freigeben, sobald Sie ihn nicht mehr benötigen. Da die CPU den Stapelspeicher so effizient organisiert, ist das Lesen und Schreiben von Stapelvariablen sehr schnell.

Mehr kann gefunden werden hier.


Der Haufen

Der Heap ist ein Bereich des Arbeitsspeichers Ihres Computers, der nicht automatisch für Sie verwaltet wird und von der CPU nicht so genau verwaltet wird. Es ist ein freier Speicherbereich (und ist größer). Um Speicher auf dem Heap zuzuweisen, müssen Sie malloc () oder calloc () verwenden, die integrierte C-Funktionen sind. Sobald Sie Speicher auf dem Heap zugewiesen haben, sind Sie dafür verantwortlich, mit free () die Zuordnung dieses Speichers aufzuheben, sobald Sie ihn nicht mehr benötigen.

Wenn Sie dies nicht tun, weist Ihr Programm einen sogenannten Speicherverlust auf. Das heißt, Speicher auf dem Heap wird weiterhin reserviert (und steht anderen Prozessen nicht zur Verfügung). Wie wir im Debugging-Abschnitt sehen werden, gibt es ein Tool namens Valgrind , mit dem Sie Speicherlecks erkennen können.

Im Gegensatz zum Stapel hat der Heap keine Größenbeschränkungen für die variable Größe (abgesehen von den offensichtlichen physischen Einschränkungen Ihres Computers). Das Lesen und Schreiben des Heapspeichers ist etwas langsamer, da auf den Speicher des Heapspeichers mit Zeigern zugegriffen werden muss. Wir werden in Kürze über Zeiger sprechen.

Im Gegensatz zum Stapel können auf die auf dem Heap erstellten Variablen von jeder Funktion in Ihrem Programm aus zugegriffen werden. Heap-Variablen haben im Wesentlichen einen globalen Gültigkeitsbereich.

Mehr kann gefunden werden hier.


Variablen, die auf dem Stapel zugeordnet sind, werden direkt im Speicher abgelegt, und der Zugriff auf diesen Speicher ist sehr schnell. Die Zuordnung wird beim Kompilieren des Programms behandelt. Wenn eine Funktion oder eine Methode eine andere Funktion aufruft, die wiederum eine andere Funktion usw. aufruft, bleibt die Ausführung all dieser Funktionen ausgesetzt, bis die allerletzte Funktion ihren Wert zurückgibt. Der Stack ist immer in der Reihenfolge LIFO) reserviert, der zuletzt reservierte Block ist immer der nächste freizugebende Block. Dies macht es wirklich einfach, den Stack zu verfolgen und einen Block aus dem zu befreien Stack ist nichts anderes als das Einstellen eines Zeigers.

Den auf dem Heap zugeordneten Variablen wird zur Laufzeit Speicher zugewiesen, und der Zugriff auf diesen Speicher ist etwas langsamer. Die Größe des Heapspeichers ist jedoch nur durch die Größe des virtuellen Speichers begrenzt. Elemente des Heaps sind nicht voneinander abhängig und können jederzeit nach dem Zufallsprinzip aufgerufen werden. Sie können jederzeit einen Block zuweisen und ihn jederzeit freigeben. Dies macht es viel komplexer zu verfolgen, welche Teile des Heaps zu einem bestimmten Zeitpunkt zugewiesen oder frei sind.

Enter image description here

Sie können den Stapel verwenden, wenn Sie genau wissen, wie viele Daten Sie vor der Kompilierung zuordnen müssen und diese nicht zu groß sind. Sie können den Heap verwenden, wenn Sie nicht genau wissen, wie viele Daten Sie zur Laufzeit benötigen oder wenn Sie viele Daten zuordnen müssen.

In einer Multithread-Situation hat jeder Thread seinen eigenen, völlig unabhängigen Stapel, aber er teilt sich den Heap. Der Stapel ist threadspezifisch und der Heap anwendungsspezifisch. Der Stack ist wichtig für die Ausnahmebehandlung und die Ausführung von Threads.

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

Enter image description here

Wenn die Anwendung zur Laufzeit mehr Heap benötigt, kann sie Arbeitsspeicher aus freiem Arbeitsspeicher zuweisen. Wenn der Stapel Arbeitsspeicher benötigt, kann er Arbeitsspeicher aus freiem Arbeitsspeicher zuweisen, der der Anwendung zugewiesen wurde.

Sogar mehr Details werden hier gegeben und hier .


Kommen Sie nun zu den Antworten Ihrer Frage .

Inwieweit werden sie von der Betriebssystem- oder Sprachlaufzeit gesteuert?

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

Mehr kann gefunden werden hier.

Welchen Umfang haben sie?

Bereits oben angegeben.

"Sie können den Stapel verwenden, wenn Sie genau wissen, wie viele Daten Sie vor dem Kompilieren zuordnen müssen, und er nicht zu groß ist. Sie können den Heap verwenden, wenn Sie nicht genau wissen, wie viele Daten Sie zur Laufzeit benötigen oder wenn Sie müssen eine Menge Daten zuordnen. "

Weitere finden Sie in hier .

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

Die Größe des Stapels wird durch OS festgelegt, wenn ein Thread erstellt wird. Die Größe des Heapspeichers wird beim Start der Anwendung festgelegt, kann jedoch bei Bedarf an Speicherplatz zunehmen (der Zuweiser fordert vom Betriebssystem mehr Speicher an).

Was macht einen schneller?

Die Stapelzuweisung ist viel schneller, da nur der Stapelzeiger bewegt wird. Mithilfe von Speicherpools können Sie eine vergleichbare Leistung bei der Heap-Zuweisung erzielen, was jedoch mit einer geringfügig erhöhten Komplexität und eigenen Kopfschmerzen verbunden ist.

Stack vs. Heap ist nicht nur ein Leistungsaspekt. es sagt auch viel über die erwartete Lebensdauer von Objekten aus.

Details finden Sie unter hier.

42
Abrar Jahin

OK, einfach und kurz gesagt, sie bedeuten geordnet und nicht geordnet ...!

Stack: In Stack-Items werden die Dinge übereinandergelegt, was bedeutet, dass die Verarbeitung schneller und effizienter ist! ... 

Es gibt also immer einen Index, der auf den jeweiligen Artikel verweist, auch die Verarbeitung wird schneller, es gibt auch eine Beziehung zwischen den Artikeln! ...

Heap: Keine Reihenfolge, die Verarbeitung wird langsamer und die Werte werden zusammen mit einer bestimmten Reihenfolge oder einem bestimmten Index durcheinander gebracht. Es gibt zufällige Werte und es gibt keine Beziehung zwischen ihnen. Daher können Ausführung und Nutzungsdauer variieren. ..

Ich erstelle das Bild unten, um zu zeigen, wie sie aussehen können:

 enter image description here

38
Alireza

In den achtziger Jahren propagierte UNIX wie Hasen mit großen Firmen, die ihr eigenes Geschäft machten. Exxon hatte eines, ebenso wie Dutzende von Markennamen, die an die Geschichte verloren gingen. Die Art der Erinnerung lag im Ermessen der vielen Implementierer.

Ein typisches C-Programm wurde flach im Speicher mit einer Gelegenheit angelegt, um durch Ändern des brk () - Werts zu erhöhen. Normalerweise lag der HEAP-Wert knapp unter diesem brk-Wert Mit zunehmendem brk erhöhte sich die verfügbare Menge Haufen.

Der einzelne STACK war typischerweise ein Bereich unter HEAP, der ein Speicherbereich war, der nichts enthielt, bis der nächste feststehende Speicherblock oben war. Dieser nächste Block war oft CODE, der durch Stapeldaten überschrieben werden konnte in einem der berühmtesten Hacks seiner Zeit.

Ein typischer Speicherblock war BSS (ein Block mit Nullwerten), der in einem Herstellerangebot versehentlich nicht auf Null gesetzt wurde. Ein anderer war DATA, der initialisierte Werte enthielt, einschließlich Zeichenfolgen und Zahlen. Ein dritter war CODE, der CRT enthielt (C-Laufzeit), Hauptfunktionen, Funktionen und Bibliotheken.

Das Aufkommen des virtuellen Speichers in UNIX ändert viele der Einschränkungen. Es gibt keinen objektiven Grund, warum diese Blöcke zusammenhängend sein müssen, Oder in der Größe festgelegt sein müssen oder eine bestimmte Art und Weise jetzt geordnet sind. Natürlich vorher UNIX war Multics, die nicht unter diesen Einschränkungen litten ... Hier ein Diagramm, das eines der Speicherlayouts dieser Zeit zeigt.

A typical 1980s style UNIX C program memory layout

35
jlettvin

stack, heap und data jedes Prozesses im virtuellen Speicher:

 stack, heap and static data

29
Yousha Aleayoub

Ein paar Cent: Ich denke, es wird gut sein, Speicher grafisch und einfacher zu zeichnen:

 This is my vision of process memory construction with simplification for more easy understanding wht happening


Pfeile - zeigen an, wo Wachstumsstapel und -heap, Prozessstapelgröße eine Begrenzung haben, die im Betriebssystem definiert ist, Thread-Stapelgrößenbeschränkungen durch Parameter in der Thread-Erstellungs-API normalerweise. Heap begrenzt in der Regel die maximale Größe des virtuellen Speichers, z. B. für 32 Bit, 2 bis 4 GB.

So einfach geht's: Prozess-Heap ist allgemein für den Prozess und alle Threads darin, und wird im Allgemeinen für die Speicherzuweisung mit malloc () verwendet.

Stack ist ein schneller Speicher zum Speichern von Funktionsrückgabepunkten und -variablen, die als Parameter in Funktionsaufrufen und lokalen Funktionsvariablen verarbeitet werden.

24

Da einige Antworten nicht mehr richtig aussahen, trage ich meine Milbe bei.

Überraschenderweise hat niemand erwähnt, dass mehrere (dh nicht mit der Anzahl der ausgeführten Threads auf Betriebssystemebene zusammenhängende) Aufrufstapel nicht nur in exotischen Sprachen (PostScript) oder Plattformen (Intel Itanium), sondern auch in Fasern , grüne Fäden und einige Implementierungen von Koroutinen .

Fasern, grüne Fäden und Koroutinen ähneln sich in vielerlei Hinsicht, was zu großer Verwirrung führt. Der Unterschied zwischen Fasern und grünen Fäden besteht darin, dass erstere kooperatives Multitasking verwenden, während letztere entweder kooperatives oder präventives (oder sogar beides) aufweisen können. Zur Unterscheidung zwischen Fasern und Koroutinen siehe hier .

In jedem Fall besteht der Zweck von Fasern, grünen Fäden und Koroutinen darin, mehrere Funktionen gleichzeitig auszuführen, aber nicht parallel (siehe this SO frage zur Unterscheidung) innerhalb eines einzelnen Threads auf Betriebssystemebene, wobei die Steuerung auf organisierte Weise voneinander übertragen wird.

Wenn Sie Fasern, grüne Fäden oder Koroutinen verwenden, haben Sie normalerweise einen separaten Stapel pro Funktion. (Technisch gesehen ist nicht nur ein Stapel, sondern ein ganzer Ausführungskontext pro Funktion. Am wichtigsten sind die CPU-Register.) Für jeden Thread gibt es so viele Stapel, wie Funktionen gleichzeitig ausgeführt werden, und der Thread wechselt zwischen der Ausführung jeder Funktion nach der Logik Ihres Programms. Wenn eine Funktion zu Ende ist, wird ihr Stapel zerstört. Anzahl und Lebensdauer der Stapel sind also dynamisch und werden nicht durch die Anzahl der Threads auf Betriebssystemebene bestimmt!

Beachte, dass ich sagte " normalerweise habe einen separaten Stack pro Funktion". Es gibt sowohl stackful als auch stackless Implementierungen von Couroutines. Die bemerkenswertesten stapelbaren C++ - Implementierungen sind Boost.Coroutine und Microsoft PPL 's async/await. (In C++ 's resumable functions (a.k.a. "async und await"), die für C++ 17 vorgeschlagen wurden, werden wahrscheinlich stapellose Coroutinen verwendet.)

Der Vorschlag für die C++ - Standardbibliothek für Fasern ist in Vorbereitung. Es gibt auch einige Drittanbieter Bibliotheken . Grüne Fäden sind in Sprachen wie Python und Ruby sehr beliebt.

21
shakurov

Ich habe etwas zu teilen, obwohl die wichtigsten Punkte bereits abgedeckt sind.

Stapel  

  • Sehr schneller Zugang.
  • Im RAM gespeichert.
  • Hier werden Funktionsaufrufe zusammen mit den übergebenen lokalen Variablen und Funktionsparametern geladen.
  • Speicherplatz wird automatisch freigegeben, wenn das Programm einen Bereich verlässt.
  • Gespeichert im sequenziellen Speicher.

Haufen

  • Langsamer Zugriff im Vergleich zu Stack.
  • Im RAM gespeichert.
  • Dynamisch erzeugte Variablen werden hier gespeichert, was nach der Verwendung den zugewiesenen Speicher freigeben muss.
  • Wird überall dort gespeichert, wo die Speicherzuweisung erfolgt, der Zugriff erfolgt immer mit dem Zeiger.

Interessante Anmerkung:

  • Wenn die Funktionsaufrufe in einem Heap gespeichert worden wären, hätte dies zu zwei unordentlichen Punkten geführt:
    1. Aufgrund der sequentiellen Speicherung im Stapel ist die Ausführung schneller. Die Speicherung in Heap hätte zu einem enormen Zeitaufwand geführt, wodurch das gesamte Programm langsamer ausgeführt werden müsste.
    2. Wenn Funktionen in einem Heap gespeichert würden (unordentlicher Speicher, auf den der Zeiger zeigt), hätte es keine Möglichkeit gegeben, zur Anruferadresse zurückzukehren (der Stack ergibt sich aufgrund der sequentiellen Speicherung im Speicher).
12

Viele Antworten sind als Konzepte richtig, aber wir müssen beachten, dass die Hardware (d. H. Der Mikroprozessor) einen Stack benötigt, um das Aufrufen von Unterprogrammen (CALL in Assembler) zu ermöglichen. (OOP Jungs nennen es Methoden)

Auf dem Stack speichern Sie die Rücksprungadressen und rufen → Push/ret → Pop direkt in der Hardware auf.

Sie können den Stack verwenden, um Parameter zu übergeben. Auch wenn er langsamer ist als Register (z. B. ein Mikroprozessor-Guru oder ein gutes BIOS-Buch aus den 1980er Jahren).

  • Ohne Stack no kann der Mikroprozessor funktionieren. (Ohne Subroutinen/Funktionen können wir uns selbst in Assembler kein Programm vorstellen.)
  • Ohne den Haufen kann es. (Ein Assembly-Sprachprogramm kann ohne funktionieren, da der Heap ein OS-Konzept ist, wie Malloc, also ein OS/Lib-Aufruf.

Stapelverwendung ist schneller als:

  • Ist Hardware und sogar Push/Pop sind sehr effizient.
  • malloc erfordert den Eintritt in den Kernel-Modus. Verwenden Sie Lock/Semaphore (oder andere Synchronisationsprimitive), um Code auszuführen, und verwalten Sie einige Strukturen, die zum Verfolgen der Zuordnung erforderlich sind.
8
ingconti

Beeindruckend! So viele Antworten und ich glaube nicht, dass einer von ihnen es richtig verstanden hat ...

1) Wo und was sind sie (physisch im Speicher eines echten Computers)?

Der Stack ist ein Speicher, der mit der höchsten Speicheradresse beginnt, die Ihrem Programmabbild zugeordnet ist, und von dort an Wert verlieren. Es ist reserviert für aufgerufene Funktionsparameter und für alle temporären Variablen, die in Funktionen verwendet werden.

Es gibt zwei Haufen: öffentlich und privat.

Der private Heapspeicher beginnt mit einer 16-Byte-Grenze (für 64-Bit-Programme) oder einer 8-Byte-Grenze (für 32-Bit-Programme) nach dem letzten Byte des Codes in Ihrem Programm und steigt dann an Wert. Es wird auch als Standardheap bezeichnet.

Wenn der private Heap zu groß wird, überlappt er den Stapelbereich, und der Stapel überlappt den Heap, wenn er zu groß wird. Da der Stack an einer höheren Adresse beginnt und sich auf eine niedrigere Adresse herunterarbeitet, können Sie bei ordnungsgemäßem Hacking den Stack so groß machen, dass er den privaten Heap-Bereich überläuft und den Codebereich überlappt. Der Trick besteht dann darin, so viel Code-Bereich zu überlappen, dass Sie den Code einhaken können. Es ist ein bisschen schwierig und Sie riskieren einen Programmabsturz, aber es ist einfach und sehr effektiv.

Der öffentliche Heap befindet sich in seinem eigenen Speicherbereich außerhalb Ihres Programmabbildbereichs. Dieser Speicher wird auf der Festplatte abgesaugt, wenn die Speicherressourcen knapp werden.

2) Inwiefern werden sie von der Betriebssystem- oder Sprachlaufzeit gesteuert?

Der Stack wird vom Programmierer gesteuert, der private Heap wird vom Betriebssystem verwaltet, und der öffentliche Heap wird nicht von jedermann kontrolliert, da es sich um einen OS-Dienst handelt. Sie stellen Anforderungen, und diese werden entweder gewährt oder abgelehnt.

2b) Welchen Umfang haben sie?

Sie sind alle für das Programm global, aber ihre Inhalte können privat, öffentlich oder global sein.

2c) Was bestimmt die Größe von jedem von ihnen?

Die Größe des Stacks und der private Heap werden von den Laufzeitoptionen des Compilers bestimmt. Der öffentliche Heapspeicher wird zur Laufzeit mit einem Größenparameter initialisiert.

2d) Was macht einen schneller?

Sie sind nicht darauf ausgelegt, schnell zu sein, sie sind nützlich, um nützlich zu sein. Wie der Programmierer sie verwendet, bestimmt, ob sie "schnell" oder "langsam" sind.

REF:

https://norasandler.com/2019/02/18/Write-a-Compiler-10.html

https://docs.Microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap

https://docs.Microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate

3
ar18

speicherzuweisungsstrategien durch Programmiersprachen 

SPEICHER IN C - DER STAPEL, DER HEAP, UND STATISCHE

 enter image description here  MEMORY IN C – THE STACK, THE HEAP, AND STATIC

  1. OP: Inwieweit werden sie von der Betriebssystem- oder Sprachlaufzeit gesteuert?

Ich möchte ein paar Dinge zu dieser wichtigen Frage hinzufügen: 

Betriebssystem und Common Language Runtime 

Schlüsselkomponenten von .NET Framework  enter image description here Net Framework 4.5-Architektur  enter image description here

Bestandteile von CLR  Components of CLR  enter image description here  enter image description here  enter image description here

0
leonidaa