Warum geht es in .Net-Büchern um die Zuordnung von Stapel- und Heapspeicher?

36

Es scheint, als würde in jedem .net-Buch von Werttypen im Vergleich zu Referenztypen die Rede sein, und es wird darauf hingewiesen, dass (häufig fälschlicherweise) angegeben wird, wo jeder Typ gespeichert ist - der Heap oder der Stack. Normalerweise ist es in den ersten Kapiteln und als eine wichtige Tatsache dargestellt. Ich denke, es ist sogar auf Zertifizierungsprüfungen abgedeckt . Warum ist Stack vs Heap für (Anfänger-) .Net-Entwickler überhaupt wichtig? Du teilst Sachen zu und es funktioniert einfach, oder?

Greg
quelle
11
Einige Autoren haben nur ein schlechtes Urteil darüber, was für Anfänger wichtig ist und was irrelevantes Rauschen ist. In einem Buch, das ich kürzlich gesehen habe, enthielt die erste Erwähnung von Zugriffsmodifikatoren bereits geschützte interne , die ich in 6 Jahren von C # ...
Timwi
1
Ich vermute, wer auch immer die ursprüngliche .Net-Dokumentation für diesen Teil geschrieben hat, hat viel daraus gemacht, und diese Dokumentation ist das, worauf die Autoren ursprünglich ihre Bücher aufgebaut haben, und dann ist sie einfach geblieben.
Greg
Die Aussage, dass Wertetypen das Ganze kopieren und Referenzen nicht, wäre sinnvoller und leichter zu verstehen, warum Referenzen verwendet werden, da der Speicherort dieser Werte implementierungsspezifisch und sogar irrelevant sein kann.
Trinidad
Frachtkult interviewen?
Den

Antworten:

37

Ich bin davon überzeugt, dass der Hauptgrund dafür, dass diese Information als wichtig erachtet wird, die Tradition ist. In nicht verwalteten Umgebungen ist die Unterscheidung zwischen Stack und Heap wichtig, und wir müssen den von uns verwendeten Speicher manuell zuweisen und löschen. Jetzt kümmert sich die Garbage Collection um die Verwaltung, sodass sie dieses Bit ignorieren. Ich glaube nicht, dass die Nachricht wirklich durchgekommen ist, dass es uns egal ist, welcher Speichertyp verwendet wird.

Wie Fede betonte, hat Eric Lippert einige sehr interessante Dinge zu sagen: http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx .

In Anbetracht dieser Informationen können Sie meinen ersten Absatz so anpassen, dass er im Grunde lautet: "Die Leute geben diese Informationen an und gehen davon aus, dass sie wichtig sind, weil falsche oder unvollständige Informationen zusammen mit dem Erfordernis dieses Wissens in der Vergangenheit vorliegen."

Für diejenigen, die es aus Performancegründen immer noch für wichtig halten: Welche Maßnahmen würden Sie ergreifen, um etwas vom Haufen auf den Stapel zu verschieben, wenn Sie Dinge messen und herausfinden würden, dass es wichtig ist? Wahrscheinlicher ist, dass Sie für den Problembereich eine völlig andere Möglichkeit zur Leistungsverbesserung finden.

John Fisher
quelle
6
Ich habe gehört, dass es in einigen Framework-Implementierungen (die speziell auf der Xbox kompakt sind) besser ist, während des Renderns Strukturen zu verwenden (das Spiel selbst), um die Garbage Collection zu reduzieren. Sie würden weiterhin normale Typen verwenden, diese sind jedoch vorab zugewiesen, damit der GC während des Spiels nicht ausgeführt wird. Das ist ungefähr die einzige mir bekannte Optimierung in Bezug auf Stack vs. Heap in .NET, und sie ist ziemlich spezifisch für die Anforderungen des kompakten Frameworks und der Echtzeitprogramme.
CodexArcanum
5
Ich stimme dem Argument der Tradition größtenteils zu. Viele erfahrene Programmierer haben möglicherweise irgendwann in einer einfachen Sprache programmiert, in der es darauf ankam, ob Sie korrekten und effizienten Code wollten. Nehmen wir zum Beispiel C ++, eine nicht verwaltete Sprache: Die offizielle Spezifikation besagt nicht, dass automatische Variablen auf dem Stack usw. abgelegt werden müssen. Der Standard von C ++ behandelt den Stack und den Heap als Implementierungsdetails. +1
Stakx
36

Es scheint, als würde in jedem .NET-Buch von Werttypen im Vergleich zu Referenztypen die Rede sein, und es wird darauf hingewiesen, dass (häufig fälschlicherweise) angegeben wird, wo jeder Typ gespeichert ist - der Heap oder der Stack. Normalerweise ist es in den ersten Kapiteln und als eine wichtige Tatsache dargestellt.

Ich stimme vollkommen zu; Ich sehe das die ganze Zeit.

Warum werden in .NET-Büchern Stapel- und Heapspeicherzuweisungen behandelt?

Ein Grund dafür ist, dass viele Menschen aus einem C- oder C ++ - Hintergrund zu C # (oder anderen .NET-Sprachen) gekommen sind. Da diese Sprachen die Regeln zur Speicherlebensdauer für Sie nicht durchsetzen, müssen Sie diese Regeln kennen und Ihr Programm sorgfältig implementieren, um sie zu befolgen.

Nun, wohl wissend , diese Regeln und nach ihnen in C nicht erforderlich , dass Sie „den Haufen“ und „den Stapel“ zu verstehen. Wenn Sie jedoch verstehen, wie die Datenstrukturen funktionieren, ist es oft einfacher, die Regeln zu verstehen und zu befolgen.

Wenn ein Anfängerbuch geschrieben wird, ist es für einen Autor selbstverständlich, die Konzepte in derselben Reihenfolge zu erklären, in der er sie gelernt hat. Dies ist nicht unbedingt die Reihenfolge, die für den Benutzer sinnvoll ist. Ich war vor kurzem technischer Redakteur für Scott Dormans C # 4-Einsteiger-Buch, und eines der Dinge, die mir gefallen haben, war, dass Scott eine ziemlich vernünftige Reihenfolge für die Themen gewählt hat, anstatt mit den wirklich fortgeschrittenen Themen im Speichermanagement zu beginnen.

Ein weiterer Grund ist, dass auf einigen Seiten in der MSDN-Dokumentation die Überlegungen zum Speicher stark betont werden. Besonders ältere MSDN-Dokumentationen, die noch aus den Anfängen stammen. Ein Großteil dieser Dokumentation weist subtile Fehler auf, die nie beseitigt wurden, und Sie müssen sich daran erinnern, dass sie zu einer bestimmten Zeit in der Geschichte und für ein bestimmtes Publikum geschrieben wurde.

Warum ist Stack vs Heap für (Anfänger-) .NET-Entwickler überhaupt wichtig?

Meiner Meinung nach nicht. Viel wichtiger ist es, Dinge zu verstehen wie:

  • Was ist der Unterschied in der Kopiersemantik zwischen einem Referenztyp und einem Werttyp?
  • Wie verhält sich ein Parameter "ref int x"?
  • Warum sollten Werttypen unveränderlich sein?

Und so weiter.

Du teilst Sachen zu und es funktioniert einfach, oder?

Das ist das Ideal.

Jetzt gibt es Situationen, in denen es wichtig ist. Garbage Collection ist fantastisch und relativ günstig, aber es ist nicht kostenlos. Das Kopieren kleiner Strukturen ist relativ kostengünstig, aber nicht kostenlos. Es gibt realistische Leistungsszenarien, in denen Sie die Kosten für den Erfassungsdruck gegen die Kosten für übermäßiges Kopieren abwägen müssen. In diesen Fällen ist es sehr hilfreich, die Größe, den Ort und die tatsächliche Lebensdauer aller relevanten Speicher genau zu kennen.

In ähnlicher Weise gibt es realistische Interop-Szenarien, in denen bekannt sein muss, was sich auf dem Stapel und auf dem Haufen befindet und was der Garbage Collector möglicherweise bewegt. Aus diesem Grund verfügt C # über Funktionen wie "fixed", "stackalloc" und so weiter.

Aber das sind alles fortgeschrittene Szenarien. Idealerweise muss sich ein Anfänger-Programmierer um nichts von alledem kümmern.

Eric Lippert
quelle
2
Danke für die Antwort Eric. Ihre letzten Blog-Beiträge zu diesem Thema haben mich tatsächlich dazu veranlasst, die Frage zu stellen.
Greg
13

Ihnen allen fehlt der Sinn. Der Grund, warum die Stapel / Heap-Unterscheidung wichtig ist, liegt im Umfang .

struct S { ... }

void f() {
    var x = new S();
    ...
 }

Sobald x den Gültigkeitsbereich verlässt, ist das erstellte Objekt kategorisch verschwunden . Das liegt nur daran, dass es auf dem Stack zugewiesen ist, nicht auf dem Heap. Es gibt nichts, was an dem "..." - Teil der Methode hätte vorbeigehen können, was diese Tatsache ändern könnte. Insbesondere könnten Zuweisungen oder Methodenaufrufe nur Kopien der S-Struktur erstellt und keine neuen Verweise darauf erstellt haben, damit sie weiterleben kann.

class C { ... }

void f() {
     var x = new C();
     ...
}

Ganz andere Geschichte! Da sich x jetzt auf dem Heap befindet , könnte sein Objekt (dh das Objekt selbst , keine Kopie davon) sehr gut weiterleben, nachdem x den Gültigkeitsbereich verlässt. In der Tat ist der einzige Weg, wie es nicht weiterleben wird, wenn x der einzige Verweis darauf ist. Wenn Zuweisungen oder Methodenaufrufe im Teil "..." andere Referenzen erstellt haben, die zum Zeitpunkt, an dem x den Gültigkeitsbereich verlässt, noch "aktiv" sind , wird dieses Objekt weiterhin aktiv sein.

Dies ist ein sehr wichtiges Konzept, und der einzige Weg, um wirklich zu verstehen, "was und warum", besteht darin, den Unterschied zwischen Stapel- und Heap-Zuweisung zu kennen.

JoelFan
quelle
Ich bin mir nicht sicher, ob ich dieses Argument bereits in Büchern zusammen mit der Stapel / Haufen-Diskussion gesehen habe, aber es ist gut. +1
Greg
2
Aufgrund der Art und Weise, wie C # Abschlüsse generiert, kann Code in the ...dazu führen x, dass er in ein Feld einer vom Compiler generierten Klasse konvertiert wird und somit den angegebenen Gültigkeitsbereich überschreitet. Persönlich finde ich die Idee des impliziten Hebens unangenehm, aber Sprachdesigner scheinen sich dafür zu entscheiden (im Gegensatz dazu, dass jede Variable, die gehisst werden soll, etwas in ihrer Deklaration enthalten muss, um dies zu spezifizieren). Um die Programmkorrektheit sicherzustellen, müssen häufig alle Verweise auf ein Objekt berücksichtigt werden. Zu wissen, dass zum Zeitpunkt der Rückkehr einer Routine keine Kopie einer übergebenen Referenz verbleibt, ist nützlich.
Supercat
1
Was die "Strukturen sind auf dem Stapel" betrifft, lautet die richtige Aussage, dass, wenn ein Speicherort als deklariert wird structType foo, der Speicherort fooden Inhalt seiner Felder enthält; Wenn foosich auf dem Stapel befindet, sind dies auch die Felder. Wenn fooes auf dem Haufen ist, sind es auch seine Felder. wenn fooin einer vernetzten Apple II ist, so sind die Felder. Wenn fooes sich hingegen um einen Klassentyp handelte, würde er entweder nulleinen Verweis auf ein Objekt enthalten. Die einzige Situation, in der foogesagt werden kann, dass der Klassentyp die Felder des Objekts enthält, wäre, wenn es das einzige Feld einer Klasse ist und einen Verweis auf sich selbst enthält.
Supercat
+1, ich liebe deine Einsicht hier und ich denke, dass sie gültig ist ... Ich glaube jedoch nicht, dass es eine Rechtfertigung dafür ist, warum Bücher dieses Thema so ausführlich behandeln. Scheint so, als ob das, was Sie hier erklärt haben, diese drei oder vier Kapitel dieses Buches ersetzen und viel hilfreicher sein könnte.
Frank V
1
Nach meinem Wissen müssen Strukturen nicht, und sie bleiben in der Tat immer auf dem Stapel.
Sara
5

Was das Thema betrifft, stimme ich @Kirk zu, dass es ein wichtiges Konzept ist, das Sie verstehen müssen. Je besser Sie die Mechanismen kennen, desto besser können Sie großartige Anwendungen erstellen, die reibungslos funktionieren.

Jetzt scheint Eric Lippert Ihnen zuzustimmen, dass das Thema von den meisten Autoren nicht richtig behandelt wird. Ich empfehle Ihnen, seinen Blog zu lesen, um ein umfassendes Verständnis dessen zu erlangen, was sich unter der Haube verbirgt.

Fede
quelle
2
Nun, Eric's Post betont, dass alles, was Sie wissen müssen, die offensichtlichen Eigenschaften von Wert- und Referenztypen sind und dass die Implementierung nicht einmal gleich bleiben sollte. Ich halte es für ziemlich fragwürdig, darauf hinzuweisen, dass es einen effizienten Weg gibt, C # ohne Stack erneut zu implementieren, aber sein Punkt ist richtig: Es ist nicht Teil der Sprachspezifikation. Der einzige Grund, diese Erklärung zu verwenden, ist, dass sie für Programmierer nützlich ist, die andere Sprachen beherrschen, insbesondere C. Solange sie eine Allegorie kennen, die in der Literatur nicht klar ist.
Jeremy
5

Nun, ich dachte, das ist der springende Punkt bei verwalteten Umgebungen. Ich würde sogar so weit gehen, dies ein Implementierungsdetail der zugrunde liegenden Laufzeit zu nennen, über das Sie KEINE Vermutungen anstellen sollten, da es sich jederzeit ändern könnte.

Ich weiß nicht viel über .NET, aber soweit ich weiß, ist es vor der Ausführung JITted. Die JIT könnte zum Beispiel eine Escape-Analyse durchführen und was nicht, und plötzlich würden Objekte auf dem Stapel oder nur in einigen Registern liegen. Das kannst du nicht wissen.

Ich nehme an, einige Bücher behandeln es einfach, weil die Autoren ihm eine große Bedeutung beimessen, oder weil sie annehmen, dass ihr Publikum dies tut (z. B. wenn Sie ein "C # für C ++ - Programmierer" geschrieben haben, sollten Sie sich wahrscheinlich mit dem Thema befassen).

Ich denke jedoch, dass es nicht viel mehr zu sagen gibt, als "Gedächtnis wird verwaltet". Andernfalls könnten die Leute falsche Schlussfolgerungen ziehen.

back2dos
quelle
2

Sie müssen wissen, wie die Speicherzuordnung funktioniert, um sie effizient zu nutzen, auch wenn Sie sie nicht explizit verwalten müssen. Dies gilt für so ziemlich jede Abstraktion in der Informatik.

Kirk Fernandes
quelle
2
In verwalteten Sprachen muss man den Unterschied zwischen einem Wertetyp und einem Referenztyp kennen, aber darüber hinaus ist es einfach, sich mit der Achse zu beschäftigen und darüber nachzudenken, wie sie unter der Haube verwaltet wird. Ein Beispiel finden Sie hier: stackoverflow.com/questions/4083981/…
Robert Harvey
Ich muss Robert
Der Unterschied zwischen zugewiesenem Heap und zugewiesenem Stack erklärt genau den Unterschied zwischen Wert- und Referenztyp.
Jeremy
3
@ Jeremy: Nicht wirklich. Siehe blogs.msdn.com/b/ericlippert/archive/2010/09/30/...
Robert Harvey
1
Jeremy, der Unterschied zwischen Heap- und Stapelzuweisung kann das unterschiedliche Verhalten zwischen Werttypen und Referenztypen nicht erklären, da es Zeiten gibt, in denen sich sowohl Werttypen als auch Referenztypen auf dem Heap befinden und sich dennoch unterschiedlich verhalten. Das Wichtigste, was Sie verstehen müssen, ist beispielsweise, wenn Sie Referenzübergabe für Referenztypen im Vergleich zu Werttypen verwenden müssen. Dies hängt nur davon ab, ob es sich um einen Werttyp oder einen Referenztyp handelt und nicht, ob es sich um einen Heap handelt.
Tim Goodman
2

Es kann einige Randfälle geben, in denen es einen Unterschied machen kann. Der Standard-Stack-Speicherplatz ist 1meg, während der Heap mehrere Gig beträgt. Wenn Ihre Lösung also eine große Anzahl von Objekten enthält, kann Ihnen der Stapelspeicherplatz ausgehen, während Sie über ausreichend Heapspeicherplatz verfügen.

Zum größten Teil ist es jedoch ziemlich akademisch.

GrumpyMonkey
quelle
Ja, aber ich bezweifle, dass eines dieser Bücher sich Mühe gibt, zu erklären, dass die Referenzen selbst auf dem Stapel gespeichert sind. Es spielt also keine Rolle, ob Sie viele Referenztypen oder viele Werttypen haben, es kann immer noch zu einem Stapelüberlauf kommen.
Jeremy
0

Wie Sie sagen, soll C # die Speicherverwaltung abstrahieren, und Heap versus Stack-Zuweisung sind Implementierungsdetails, über die der Entwickler theoretisch nichts wissen sollte.

Das Problem ist, dass einige Dinge auf intuitive Weise nur schwer zu erklären sind, ohne auf diese Implementierungsdetails Bezug zu nehmen. Versuchen Sie, das beobachtbare Verhalten zu erklären, wenn Sie Typen mit veränderlichen Werten ändern. Es ist fast unmöglich, auf die Unterscheidung zwischen Stapel und Heap zu verzichten. Oder versuchen Sie zu erklären, warum überhaupt Wertetypen in der Sprache vorhanden sind und wann Sie sie verwenden würden? Sie müssen den Unterschied verstehen, um die Sprache zu verstehen.

Beachten Sie, dass Bücher über Python oder JavaScript nicht viel bewirken, wenn sie es überhaupt erwähnen. Dies liegt daran, dass alles entweder Heap zugewiesen oder unveränderlich ist, was bedeutet, dass die verschiedenen Kopiersemantiken niemals ins Spiel kommen. In diesen Sprachen funktioniert die Abstraktion des Gedächtnisses, in C # ist sie undicht.

JacquesB
quelle