Speicherzuordnung: Stack vs Heap?

83

Ich bin verwirrt mit den Grundlagen der Speicherzuweisung zwischen Stack und Heap . Gemäß der Standarddefinition (Dinge, die jeder sagt) werden alle Werttypen einem Stapel zugewiesen und Referenztypen werden in den Heap verschoben .

Betrachten Sie nun das folgende Beispiel:

class MyClass
{
    int myInt = 0;    
    string myString = "Something";
}

class Program
{
    static void Main(string[] args)
    {
       MyClass m = new MyClass();
    }
}

Wie erfolgt nun die Speicherzuweisung in c #? Wird das Objekt von MyClass(das heißt m) vollständig dem Heap zugeordnet? Das heißt, int myIntund string myStringbeide werden auf Haufen gehen?

Oder wird das Objekt in zwei Teile geteilt und den beiden Speicherorten Stack und Heap zugewiesen?

Mrinal
quelle
Ich habe einfach nach oben gestimmt, weil ich glaube, dass es einige gute Antworten geben wird, obwohl diese lang gehegten dummen Aussagen falsch sind. Es ist jedoch ziemlich einfach, triviale Gegenargumente gegen die vorgeschlagene "doppelte Zuordnung" zu finden (Hinweis: Ein Klassenobjekt kann - und viele tun es oft - über Funktionsaufrufgrenzen hinweg leben).
Beantwortet das deine Frage? Was und wo sind der Stapel und der Haufen?
Olivier Rogier

Antworten:

55

mwird auf dem Heap zugewiesen, und das schließt ein myInt. Die Situationen, in denen primitive Typen (und Strukturen) auf dem Stapel zugewiesen werden, treten während des Methodenaufrufs auf, wodurch Platz für lokale Variablen auf dem Stapel zugewiesen wird (weil dies schneller ist). Zum Beispiel:

class MyClass
{
    int myInt = 0;

    string myString = "Something";

    void Foo(int x, int y) {
       int rv = x + y + myInt;
       myInt = 2^rv;
    }
}

rv, x, yWerden alle auf dem Stapel sein. myIntbefindet sich irgendwo auf dem Heap (und muss über den thisZeiger zugänglich sein ).

Schlamm
quelle
7
Ein wichtiger Nachtrag ist, sich daran zu erinnern, dass "der Stapel" und "der Heap" wirklich Implementierungsdetails in .NET sind. Es ist durchaus möglich, eine legale Implementierung von C # zu erstellen, die überhaupt keine stapelbasierte Zuordnung verwendet.
JSB 20
5
Ich bin damit einverstanden, dass sie so behandelt werden sollten, aber es ist nicht ganz richtig, dass es sich lediglich um Implementierungsdetails handelt. Dies wird in der öffentlichen API-Dokumentation und im Sprachstandard (EMCA-334, ISO / IEC 23270: 2006) ausdrücklich erwähnt (dh "Strukturwerte werden" auf dem Stapel "gespeichert. Sorgfältige Programmierer können manchmal die Leistung durch den umsichtigen Einsatz von Strukturen verbessern. ") Aber ja, wenn die Geschwindigkeit der Heap-Zuweisung ein Engpass für Ihre Anwendung ist, machen Sie es wahrscheinlich falsch (oder verwenden die falsche Sprache).
Schlamm
65

Sie sollten die Frage, wo Objekte zugewiesen werden, als Implementierungsdetail berücksichtigen . Es spielt für Sie keine Rolle, wo genau die Bits eines Objekts gespeichert sind. Es kann wichtig sein, ob ein Objekt ein Referenztyp oder ein Werttyp ist, aber Sie müssen sich keine Gedanken darüber machen, wo es gespeichert wird, bis Sie das Verhalten der Speicherbereinigung optimieren müssen.

Während Referenztypen in aktuellen Implementierungen immer auf dem Heap zugewiesen werden, können Werttypen auf dem Stapel zugewiesen werden - müssen dies aber nicht. Ein Werttyp wird nur dann auf dem Stapel zugewiesen, wenn es sich um eine nicht maskierte lokale oder temporäre Variable ohne Box handelt, die nicht in einem Referenztyp enthalten und nicht in einem Register zugeordnet ist.

  • Wenn ein Werttyp Teil einer Klasse ist (wie in Ihrem Beispiel), landet er auf dem Heap.
  • Wenn es verpackt ist, landet es auf dem Haufen.
  • Wenn es sich in einem Array befindet, landet es auf dem Heap.
  • Wenn es sich um eine statische Variable handelt, landet sie auf dem Heap.
  • Wenn es von einem Verschluss erfasst wird, landet es auf dem Haufen.
  • Wenn es in einem Iterator oder einem asynchronen Block verwendet wird, landet es auf dem Heap.
  • Wenn es durch unsicheren oder nicht verwalteten Code erstellt wird, kann es in jeder Art von Datenstruktur zugewiesen werden (nicht unbedingt in einem Stapel oder einem Heap).

Gibt es etwas, das ich verpasst habe?

Natürlich wäre ich mir nicht sicher, wenn ich nicht auf Eric Lipperts Beiträge zum Thema verlinken würde:

Gabe
quelle
1
Ed: Genau wann ist das wichtig?
Gabe
1
@Gabe: Es spielt keine Rolle, wo die Bits gespeichert sind. Wenn Sie beispielsweise einen Absturzspeicherauszug debuggen, werden Sie nur dann weit kommen, wenn Sie wissen, wo Sie nach Objekten / Daten suchen müssen.
Brian Rasmussen
14
Die Situationen, die Sie verpasst haben, sind: Wenn der Werttyp aus nicht verwaltetem Code stammt, auf den über einen unsicheren Zeiger zugegriffen wird, befindet er sich möglicherweise weder auf dem Stapel noch auf dem verwalteten Heap. Möglicherweise befindet es sich auf dem nicht verwalteten Heap oder in einer Datenstruktur, die nicht einmal ein Heap ist. Die ganze Idee, dass es "den Haufen" gibt, ist auch ein Mythos. Es kann Dutzende von Haufen geben. Wenn der Jitter den Wert registriert, befindet er sich nicht auf dem Stapel oder dem Heap, sondern in einem Register.
Eric Lippert
1
Eric Lipperts zweiter Teil war eine fantastische Lektüre, danke für den Link!
Dan Bechard
1
Dies ist wichtig, weil es in Interviews gefragt wird, aber nicht im wirklichen Leben. :)
Mayank
22

"Alle VALUE-Typen werden dem Stapel zugewiesen" ist sehr, sehr falsch. Strukturvariablen können als Methodenvariablen auf dem Stapel leben. Felder in einem Typ leben jedoch mit diesem Typ . Wenn der deklarierende Typ eines Felds eine Klasse ist, befinden sich die Werte als Teil dieses Objekts auf dem Heap . Wenn der deklarierende Typ eines Feldes eine Struktur ist, sind die Felder Teil dieser Struktur, wo immer diese Struktur lebt.

Sogar Methodenvariablen können sich auf dem Heap befinden, wenn sie erfasst werden (Lambda / Anon-Methode) oder Teil (zum Beispiel) eines Iteratorblocks.

Marc Gravell
quelle
1
Und vergessen Sie nicht das Boxen: Wenn Sie object x = 12;eine Methode haben, wird die 12 auf dem Heap gespeichert, obwohl es sich um eine Ganzzahl (einen Werttyp) handelt.
Gabe
@Gabe: Wertspeicherorte enthalten die Felder (öffentlich und privat) eines Werttyps in sich. Speicherorte vom Referenztyp sind entweder enthalten nulloder ein Verweis auf ein Heap-Objekt des entsprechenden Typs. Für jeden Werttyp gibt es einen entsprechenden Heap-Objekttyp; Wenn Sie versuchen, einen Werttyp in einem Speicherort vom Referenztyp zu speichern, wird ein neues Objekt des entsprechenden Heap-Objekttyps erstellt, alle Felder in dieses neue Objekt kopiert und ein Verweis auf das Objekt im Speicherort des Referenztyps gespeichert. C # gibt vor, dass der
Werttyp
... ein solcher Standpunkt führt eher zu Verwirrung als zu Verständnis. Ein Unboxed, List<T>.Enumeratordas in einer Variablen dieses Typs gespeichert ist, weist eine Wertesemantik auf , da es sich um einen Wertetyp handelt. A, List<T>.Enumeratordas in einer Variablen vom Typ gespeichert ist IEnumerator<T>, verhält sich jedoch wie ein Referenztyp. Betrachtet man den letzteren als einen anderen Typ als den ersteren, so ist der Unterschied im Verhalten leicht zu erklären. Wenn Sie so tun, als wären sie vom selben Typ, ist es viel schwieriger, über sie nachzudenken.
Supercat
11

Hervorragende Erklärung:

jgauffin
quelle
Dies war die beste Antwort für mich :)
Java Student
2

Stapel

Das stackist ein Speicherblock zum Speichern local variablesund parameters. Der Stapel wächst und schrumpft logischerweise, wenn eine Funktion eingegeben und beendet wird.

Betrachten Sie die folgende Methode:

public static int Factorial (int x)
{
    if (x == 0) 
    {
        return 1;
    }

    return x * Factorial (x - 1);
}

Diese Methode ist rekursiv, dh sie ruft sich selbst auf. Jedes Mal, wenn die Methode eingegeben wird, wird ein neues int auf dem Stapel zugewiesen , und jedes Mal , wenn die Methode beendet wird, wird das int freigegeben .


Haufen

  • Der Heap ist ein Speicherblock, in dem sich objects(dh reference-type instances) befindet. Jedes Mal, wenn ein neues Objekt erstellt wird, wird es auf dem Heap zugewiesen und ein Verweis auf dieses Objekt wird zurückgegeben. Während der Ausführung eines Programms füllt sich der Heap, wenn neue Objekte erstellt werden. Die Laufzeit verfügt über einen Garbage Collector, der regelmäßig die Zuordnung von Objekten vom Heap aufhebt, sodass Ihr Programm nicht ausgeführt wird Out Of Memory. Ein Objekt kann freigegeben werden, sobald es von nichts selbst referenziert wird alive.
  • Der Haufen speichert auch static fields. Im Gegensatz zu Objekten, die auf dem Heap zugeordnet sind (die durch Müll gesammelt werden können) these live until the application domain is torn down.

Betrachten Sie die folgende Methode:

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder ref1 = new StringBuilder ("object1");
        Console.WriteLine (ref1);
        // The StringBuilder referenced by ref1 is now eligible for GC.

        StringBuilder ref2 = new StringBuilder ("object2");
        StringBuilder ref3 = ref2;
        // The StringBuilder referenced by ref2 is NOT yet eligible for GC.
        Console.WriteLine (ref3); // object2
    }
}    

Im obigen Beispiel erstellen wir zunächst ein StringBuilder-Objekt, auf das die Variable ref1 verweist, und schreiben dann dessen Inhalt aus. Dieses StringBuilder-Objekt ist dann sofort für die Speicherbereinigung berechtigt, da es anschließend von nichts verwendet wird. Anschließend erstellen wir einen weiteren StringBuilder, auf den durch die Variable ref2 verwiesen wird, und kopieren diesen Verweis auf ref3. Obwohl ref2 nach diesem Zeitpunkt nicht mehr verwendet wird, behält ref3 dasselbe StringBuilder-Objekt bei. So wird sichergestellt, dass es erst nach Abschluss von ref3 für die Erfassung geeignet ist.

Wertinstanzen (und Objektreferenzen) leben überall dort, wo die Variable deklariert wurde. Wenn die Instanz als Feld innerhalb eines Klassentyps oder als Array-Element deklariert wurde, befindet sich diese Instanz auf dem Heap.

Sina Lotfi
quelle
1

einfache Maßnahmen

Der Werttyp kann auf THE STACK festgelegt werden. Dies ist das Implementierungsdetail, das der futuristischen Datenstruktur zugewiesen werden kann.

Daher ist es besser zu verstehen, wie Wert und Referenztyp funktionieren. Der Werttyp wird nach Wert kopiert, dh wenn Sie einen Werttyp als Parameter an eine FUNKTION übergeben, als wenn er von Natur aus kopiert wird, bedeutet dies, dass Sie eine vollständig neue Kopie haben .

Referenztypen werden als Referenz übergeben (wenn Sie nicht berücksichtigen, dass Referenz in einigen zukünftigen Versionen eine Adresse erneut speichert, kann sie in einigen anderen Datenstrukturen gespeichert werden.)

also in deinem Fall

myInt ist ein int, das in einer Klasse zusammengefasst ist, die einen Referenztyp enthält, sodass es an die Instanz der Klasse gebunden ist, die in 'THE HEAP' gespeichert wird.

Ich würde vorschlagen, Sie können anfangen, Blogs zu lesen, die von ERIC LIPPERTS geschrieben wurden.

Erics Blog

TalentTuner
quelle
1

Jedes Mal, wenn ein Objekt darin erstellt wird, wird es in den als Heap bezeichneten Speicherbereich verschoben. Die primitiven Variablen wie int und double werden im Stapel zugewiesen, wenn es sich um lokale Methodenvariablen handelt, und im Heap, wenn es sich um Mitgliedsvariablen handelt. In Methoden werden lokale Variablen in den Stapel verschoben, wenn eine Methode aufgerufen wird, und der Stapelzeiger wird dekrementiert, wenn ein Methodenaufruf abgeschlossen ist. In einer Multithread-Anwendung hat jeder Thread seinen eigenen Stapel, teilt sich jedoch denselben Heap. Aus diesem Grund sollte in Ihrem Code darauf geachtet werden, dass keine gleichzeitigen Zugriffsprobleme im Heap-Bereich auftreten. Der Stapel ist threadsicher (jeder Thread hat seinen eigenen Stapel), aber der Heap ist nicht threadsicher, es sei denn, er wird durch die Synchronisierung durch Ihren Code geschützt.

Dieser Link ist auch nützlich http://www.programmerinterview.com/index.php/data-structures/difference-between-stack-and-heap/

Rohit Goyal
quelle
0

m ist eine Referenz auf ein Objekt von MyClass, sodass m im Stapel des Hauptthreads gespeichert wird, das Objekt von MyClass jedoch im Heap. Daher speichern myInt und myString im Heap. Beachten Sie, dass m nur eine Referenz (eine Adresse zum Speicher) ist und sich auf dem Hauptstapel befindet. Wenn die Zuordnung aufgehoben wurde, löscht GC das MyClass-Objekt aus dem Heap. Weitere Informationen finden Sie in allen vier Teilen dieses Artikels unter https://www.c-sharpcorner.com/article/C-Sharp-heaping-vs-stacking-in-net- Teil-i /

Ali Afshari
quelle