Warum hat C ++ keinen Garbage Collector?


269

Ich stelle diese Frage nicht wegen der Vorzüge der Speicherbereinigung. Mein Hauptgrund für diese Frage ist, dass ich weiß, dass Bjarne Stroustrup gesagt hat, dass C ++ irgendwann einen Garbage Collector haben wird.

Warum wurde es nicht hinzugefügt? Es gibt bereits einige Garbage Collectors für C ++. Ist dies nur eines dieser Dinge, die "leichter gesagt als getan" sind? Oder gibt es andere Gründe, warum es nicht hinzugefügt wurde (und in C ++ 11 nicht hinzugefügt wird)?

Querverbindungen:

Zur Verdeutlichung verstehe ich die Gründe, warum C ++ bei seiner ersten Erstellung keinen Garbage Collector hatte. Ich frage mich, warum der Sammler nicht hinzugefügt werden kann.


26
Dies ist einer der zehn wichtigsten Mythen über C ++, die die Hasser immer wieder ansprechen. Die Garbage Collection ist nicht "eingebaut", aber es gibt verschiedene einfache Möglichkeiten, dies in C ++ zu tun. Einen Kommentar
posten,

5
Aber das ist der springende Punkt, wenn man nicht eingebaut ist, muss man es selbst machen. Realisierbarkeit von hoch nach niedrig: eingebaut, Bibliothek, hausgemacht. Ich benutze C ++ selbst und definitiv keinen Hasser, weil es die beste Sprache der Welt ist. Aber dynamisches Speichermanagement ist ein Schmerz.
QBziZ

4
@Davr - Ich bin kein C ++ - Hasser und versuche auch nicht zu argumentieren, dass C ++ einen Garbage Collector benötigt. Ich frage, weil ich weiß, dass Bjarne Stroustrup gesagt hat, dass es hinzugefügt wird, und war nur neugierig, was die Gründe dafür waren, es nicht zu implementieren.
Jason Baker

1
Dieser Artikel Der Boehm Collector für C und C ++ von Dr. Dobbs beschreibt einen Open Source Garbage Collector, der sowohl mit C als auch mit C ++ verwendet werden kann. Es werden einige der Probleme erläutert, die bei der Verwendung eines Garbage Collector mit C ++ - Destruktoren sowie der C-Standardbibliothek auftreten.
Richard Chambers

1
@rogerdpack: Aber es ist momentan nicht so nützlich (siehe meine Antwort ...), daher ist es unwahrscheinlich, dass Implementierungen in eine solche investieren.
Einpoklum

Antworten:


159

Eine implizite Speicherbereinigung hätte hinzugefügt werden können, aber sie hat den Schnitt einfach nicht geschafft. Wahrscheinlich nicht nur aufgrund von Komplikationen bei der Implementierung, sondern auch, weil Menschen nicht schnell genug zu einem allgemeinen Konsens gelangen können.

Ein Zitat von Bjarne Stroustrup selbst:

Ich hatte gehofft, dass ein Garbage Collector, der optional aktiviert werden könnte, Teil von C ++ 0x sein würde, aber es gab genug technische Probleme, die ich nur mit einer detaillierten Spezifikation der Integration eines solchen Collectors in den Rest der Sprache bewältigen musste , falls vorhanden. Wie bei im Wesentlichen allen C ++ 0x-Funktionen existiert eine experimentelle Implementierung.

Es gibt eine gute Diskussion des Themas hier .

Gesamtübersicht:

C ++ ist sehr leistungsfähig und ermöglicht es Ihnen, fast alles zu tun. Aus diesem Grund werden nicht automatisch viele Dinge auf Sie übertragen, die sich auf die Leistung auswirken könnten. Die Speicherbereinigung kann einfach mit intelligenten Zeigern implementiert werden (Objekte, die Zeiger mit einem Referenzzähler umschließen, die sich automatisch löschen, wenn der Referenzzähler 0 erreicht).

C ++ wurde für Konkurrenten entwickelt, die keine Speicherbereinigung hatten. Effizienz war das Hauptanliegen, vor dem C ++ Kritik im Vergleich zu C und anderen abwehren musste.

Es gibt 2 Arten der Speicherbereinigung ...

Explizite Speicherbereinigung:

C ++ 0x verfügt über eine Garbage Collection über Zeiger, die mit shared_ptr erstellt wurden

Wenn Sie es wollen, können Sie es benutzen, wenn Sie es nicht wollen, werden Sie nicht gezwungen, es zu benutzen.

Sie können derzeit auch boost: shared_ptr verwenden, wenn Sie nicht auf C ++ 0x warten möchten.

Implizite Speicherbereinigung:

Es gibt jedoch keine transparente Speicherbereinigung. Es wird jedoch ein Schwerpunkt für zukünftige C ++ - Spezifikationen sein.

Warum hat Tr1 keine implizite Speicherbereinigung?

Es gibt eine Menge Dinge, die tr1 von C ++ 0x hätte haben sollen, Bjarne Stroustrup erklärte in früheren Interviews, dass tr1 nicht so viel hatte, wie er gerne hätte.


71
Ich würde sich ein Hasser , wenn C ++ Garbage Collection auf mich gezwungen! Warum können die Leute nicht benutzen smart_ptr's? Wie würden Sie Low-Level-Unix-Forking mit einem Garbage Collector im Weg machen? Andere Dinge wären betroffen, wie z. B. das Einfädeln. Python hat seine globale Interpreter-Sperre hauptsächlich aufgrund seiner Speicherbereinigung (siehe Cython). Halten Sie es aus C / C ++ heraus, danke.
Unixman83

26
@ unixman83: Das Hauptproblem bei std::shared_ptrder Speicherbereinigung mit Referenzzählung (dh ) sind zyklische Referenzen, die einen Speicherverlust verursachen. Daher müssen Sie vorsichtig verwenden std::weak_ptr, um Zyklen zu unterbrechen, was unordentlich ist. Mark and Sweep Style GC hat dieses Problem nicht. Es gibt keine inhärente Inkompatibilität zwischen Threading / Forking und Garbage Collection. Java und C # verfügen beide über hochleistungsfähiges präventives Multithreading und einen Garbage Collector. Es gibt Probleme mit Echtzeitanwendungen und einem Garbage Collector, da die meisten Garbage Collectors die Welt stoppen müssen, um ausgeführt zu werden.
Andrew Tomazos

9
"Das Hauptproblem bei der referenzgezählten Speicherbereinigung (dh std::shared_ptr) sind zyklische Referenzen" und eine schreckliche Leistung, die ironisch ist, da eine bessere Leistung normalerweise die Rechtfertigung für die Verwendung von C ++ ist ... flogfrogblog.blogspot.co.uk/2011/01/…
Jon Harrop

11
"Wie würden Sie Low-Level-Unix-Forking machen?" Genauso wie GC'd-Sprachen wie OCaml dies seit ~ 20 Jahren oder länger tun.
Jon Harrop

9
"Python hat seine globale Interpretersperre hauptsächlich wegen seiner Speicherbereinigung". Strawman Argument. Java und .NET haben beide GCs, aber keine globalen Sperren.
Jon Harrop

149

Hier zur Debatte hinzufügen.

Es sind Probleme mit der Speicherbereinigung bekannt, und wenn Sie diese verstehen, können Sie besser verstehen, warum es in C ++ keine gibt.

1. Leistung?

Die erste Beschwerde betrifft oft die Leistung, aber die meisten Leute wissen nicht wirklich, wovon sie sprechen. Wie Martin Beckettdas Problem zeigt, ist möglicherweise nicht die Leistung an sich, sondern die Vorhersagbarkeit der Leistung.

Derzeit sind zwei GC-Familien weit verbreitet:

  • Mark-And-Sweep Art
  • Referenzzählart

Das Mark And Sweep ist schneller (weniger Einfluss auf die Gesamtleistung), leidet jedoch an einem "Freeze the World" -Syndrom: Wenn der GC startet, wird alles andere gestoppt, bis der GC seine Bereinigung durchgeführt hat. Wenn Sie einen Server erstellen möchten, der in wenigen Millisekunden antwortet ... werden einige Transaktionen nicht Ihren Erwartungen entsprechen :)

Das Problem von Reference Countingist anders: Referenzzählung erhöht den Overhead, insbesondere in Multithreading-Umgebungen, da Sie eine Atomzählung benötigen. Darüber hinaus gibt es das Problem der Referenzzyklen, sodass Sie einen cleveren Algorithmus benötigen, um diese Zyklen zu erkennen und zu eliminieren (im Allgemeinen wird dies auch durch ein "Einfrieren der Welt" implementiert, wenn auch weniger häufig). Im Allgemeinen ist diese Art ab heute (obwohl sie normalerweise reaktionsschneller ist oder eher seltener einfriert) langsamer als die Mark And Sweep.

Ich habe ein Papier von Eiffel-Implementierern gesehen, die versuchten, einen Reference CountingGarbage Collector zu implementieren , der eine ähnliche globale Leistung wie Mark And Sweepohne den Aspekt "Freeze The World" aufweisen würde. Für den GC war ein separater Thread erforderlich (typisch). Der Algorithmus war (am Ende) etwas beängstigend, aber das Papier hat es gut gemacht, die Konzepte einzeln vorzustellen und die Entwicklung des Algorithmus von der "einfachen" Version zur vollwertigen Version zu zeigen. Empfohlene Lektüre, wenn ich nur die PDF-Datei wieder in die Hand nehmen könnte ...

2. Ressourcenbeschaffung ist Initialisierung (RAII)

Es ist eine gängige Redewendung in C++ dass Sie den Besitz von Ressourcen in ein Objekt einschließen, um sicherzustellen, dass sie ordnungsgemäß freigegeben werden. Es wird hauptsächlich für den Speicher verwendet, da wir keine Speicherbereinigung haben, aber es ist trotzdem für viele andere Situationen nützlich:

  • Sperren (Multithread, Dateihandle, ...)
  • Verbindungen (zu einer Datenbank, einem anderen Server, ...)

Die Idee ist, die Lebensdauer des Objekts richtig zu steuern:

  • es sollte so lange am Leben sein, wie Sie es brauchen
  • Es sollte getötet werden, wenn Sie damit fertig sind

Das Problem von GC ist, dass, wenn es bei ersteren hilft und letztendlich garantiert, dass später ... dieses "ultimative" möglicherweise nicht ausreicht. Wenn Sie eine Sperre aufheben, möchten Sie wirklich, dass sie jetzt freigegeben wird, damit keine weiteren Anrufe blockiert werden!

Sprachen mit GC haben zwei Problemumgehungen:

  • Verwenden Sie GC nicht, wenn die Stapelzuweisung ausreicht: Dies gilt normalerweise für Leistungsprobleme. In unserem Fall ist dies jedoch sehr hilfreich, da der Bereich die Lebensdauer definiert
  • usingKonstrukt ... aber es ist explizit (schwach) RAII, während in C ++ RAII implizit ist, so dass der Benutzer den Fehler NICHT unabsichtlich machen kann (indem er das usingSchlüsselwort weglässt )

3. Intelligente Zeiger

Intelligente Zeiger werden häufig als Silberkugel für den Speicher angezeigt C++. Oft habe ich gehört: Wir brauchen doch keine GC, da wir intelligente Zeiger haben.

Man könnte nicht falscher sein.

Intelligente Zeiger helfen: auto_ptrund unique_ptrverwenden RAII-Konzepte, die in der Tat äußerst nützlich sind. Sie sind so einfach, dass Sie sie ganz einfach selbst schreiben können.

Wenn man das Eigentum teilen muss, wird es jedoch schwieriger: Sie können es unter mehreren Threads teilen, und es gibt einige subtile Probleme bei der Behandlung der Zählung. Deshalb geht man natürlich in Richtungshared_ptr .

Es ist großartig, dafür ist Boost schließlich, aber es ist keine Silberkugel. Tatsächlich besteht das Hauptproblem darin, shared_ptrdass es einen GC emuliert, der von implementiert wurde, Reference Countingaber Sie müssen die Zykluserkennung selbst implementieren ... Urg

Natürlich gibt es dieses weak_ptrDing, aber ich habe leider bereits Speicherlecks gesehen, obwohl shared_ptrdiese Zyklen verwendet wurden ... und wenn Sie sich in einer Umgebung mit mehreren Threads befinden, ist es äußerst schwierig, sie zu erkennen!

4. Was ist die Lösung?

Es gibt keine Silberkugel, aber wie immer ist es definitiv machbar. In Abwesenheit von GC muss klar sein, was das Eigentum betrifft:

  • Wenn möglich, bevorzugen Sie es, einen einzelnen Eigentümer zu einem bestimmten Zeitpunkt zu haben
  • Wenn nicht, stellen Sie sicher, dass Ihr Klassendiagramm keinen Zyklus bezüglich des Eigentums enthält, und brechen Sie sie mit subtiler Anwendung von weak_ptr

In der Tat wäre es großartig, einen GC zu haben ... aber es ist kein triviales Problem. Und in der Zwischenzeit müssen wir nur die Ärmel hochkrempeln.


2
Ich wünschte, ich könnte zwei Antworten akzeptieren! Das ist einfach toll. In Bezug auf die Leistung ist zu beachten, dass der GC, der in einem separaten Thread ausgeführt wird, eigentlich ziemlich häufig ist (er wird in Java und .Net verwendet). Zugegeben, das ist in eingebetteten Systemen möglicherweise nicht akzeptabel.
Jason Baker

14
Nur zwei Typen? Wie wäre es mit dem Kopieren von Sammlern? Generationssammler? Verschiedene gleichzeitige Sammler (einschließlich Baker's hartes Echtzeit-Laufband)? Verschiedene Hybridsammler? Mann, die bloße Unwissenheit in der Branche auf diesem Gebiet überrascht mich manchmal.
NUR MEINE RICHTIGE MEINUNG

12
Habe ich gesagt, dass es nur 2 Typen gibt? Ich sagte, dass es 2 gab, die weit verbreitet waren. Soweit ich weiß, verwenden Python, Java und C # jetzt alle Mark- und Sweep-Algorithmen (Java hatte früher einen Referenzzählalgorithmus). Um noch genauer zu sein, scheint es mir, dass C # Generational GC für kleinere Zyklen, Mark And Sweep für große Zyklen und Kopieren verwendet, um die Speicherfragmentierung zu bekämpfen. obwohl ich argumentieren würde, dass das Herz des Algorithmus Mark And Sweep ist. Kennen Sie eine Mainstream-Sprache, die eine andere Technologie verwendet? Ich bin immer froh zu lernen.
Matthieu M.

3
Sie haben gerade eine Hauptsprache benannt, in der drei verwendet wurden.
NUR MEINE RICHTIGE MEINUNG

3
Der Hauptunterschied besteht darin, dass Generational und Incremental GC die Welt nicht stoppen müssen, um zu funktionieren, und Sie können sie auf Single-Threaded-Systemen ohne zu viel Overhead arbeiten lassen, indem Sie gelegentlich eine Iteration der Baumdurchquerung durchführen, wenn Sie auf die GC-Zeiger zugreifen (der Faktor) kann durch die Anzahl der neuen Knoten zusammen mit einer grundlegenden Vorhersage der Notwendigkeit des Sammelns bestimmt werden). Sie können GC noch weiter vorantreiben, indem Sie Daten darüber einfügen, wo im Code die Erstellung / Änderung des Knotens stattgefunden hat, wodurch Sie Ihre Vorhersagen verbessern können, und Sie erhalten damit kostenlos Escape Analysis.
Keldon Alleyne

56

Welche Art? sollte es für eingebettete Waschmaschinensteuerungen, Handys, Workstations oder Supercomputer optimiert werden?
Sollte es die Reaktionsfähigkeit der Benutzeroberfläche oder das Laden des Servers priorisieren?
sollte es viel Speicher oder viel CPU verbrauchen?

C / c ++ wird unter einfach zu vielen verschiedenen Umständen verwendet. Ich vermute, dass so etwas wie Boost Smart Pointers für die meisten Benutzer ausreichen wird

Bearbeiten - Automatische Garbage Collectors sind weniger ein Leistungsproblem (Sie können immer mehr Server kaufen), sondern eine Frage der vorhersehbaren Leistung.
Nicht zu wissen, wann der GC einschalten wird, ist wie die Einstellung eines narkoleptischen Airline-Piloten. Meistens sind sie großartig - aber wenn Sie wirklich Reaktionsfähigkeit brauchen!


6
Ich verstehe Ihren Standpunkt definitiv, aber ich fühle mich gezwungen zu fragen: Wird Java nicht in ungefähr so ​​vielen Anwendungen verwendet?
Jason Baker

35
Nein. Java ist nicht für Hochleistungsanwendungen geeignet, da es keine Leistungsgarantien im gleichen Umfang wie C ++ bietet. Sie finden es also in einem Mobiltelefon, aber nicht in einem Mobilfunkschalter oder Supercomputer.
Zathrus

11
Sie können immer mehr Server kaufen, aber Sie können nicht immer mehr CPU für das Handy kaufen, das sich bereits in der Tasche des Kunden befindet!
Crashworks

8
Java hat die CPU-Effizienz erheblich verbessert. Das wirklich unlösbare Problem ist die Speichernutzung. Java ist von Natur aus weniger speichereffizient als C ++. Und diese Ineffizienz ist auf die Tatsache zurückzuführen, dass es sich um gesammelten Müll handelt. Die Speicherbereinigung kann nicht sowohl schnell als auch speichereffizient sein. Dies wird deutlich, wenn Sie sich ansehen, wie schnell GC-Algorithmen funktionieren.
Nate CK

2
@Zathrus Java kann beim Durchsatz b / c des optimierenden JIT gewinnen, jedoch nicht bei der Latenz (Boo-Echtzeit) und schon gar nicht beim Speicherbedarf.
Gtrak

34

Einer der Hauptgründe, warum C ++ keine Garbage Collection eingebaut hat, ist, dass es sehr, sehr schwierig ist, die Garbage Collection dazu zu bringen, mit Destruktoren gut zu spielen. Soweit ich weiß, weiß noch niemand wirklich, wie man es vollständig löst. Es gibt viele Probleme zu lösen:

  • deterministische Lebensdauern von Objekten (Referenzzählung gibt Ihnen dies, GC jedoch nicht. Obwohl es möglicherweise keine so große Sache ist).
  • Was passiert, wenn ein Destruktor wirft, wenn das Objekt Müll gesammelt wird? Die meisten Sprachen ignorieren diese Ausnahme, da es wirklich keinen Catch-Block gibt, zu dem sie transportiert werden können, aber dies ist wahrscheinlich keine akzeptable Lösung für C ++.
  • Wie aktiviere / deaktiviere ich es? Natürlich wäre es wahrscheinlich eine Entscheidung zur Kompilierungszeit, aber Code, der für GC geschrieben wurde, und Code, der für NOT GC geschrieben wurde, wird sehr unterschiedlich und wahrscheinlich inkompatibel sein. Wie vereinbaren Sie das?

Dies sind nur einige der Probleme, mit denen wir konfrontiert sind.


17
GC und Destruktoren sind ein gelöstes Problem, durch einen schönen Seitenschritt von Bjarne. Destruktoren werden während der GC nicht ausgeführt, da dies nicht der Punkt der GC ist. GC in C ++ dient dazu, den Begriff des unendlichen Speichers zu erstellen , nicht der unendlichen anderen Ressourcen.
MSalters

2
Wenn Destruktoren nicht ausgeführt werden, ändert sich die Semantik der Sprache vollständig. Ich denke, zumindest würden Sie ein neues Schlüsselwort "gcnew" oder etwas anderes benötigen, damit Sie explizit zulassen, dass dieses Objekt GC-fähig ist (und Sie sollten es daher nicht verwenden, um Ressourcen neben dem Speicher zu verpacken).
Greg Rogers

7
Dies ist ein falsches Argument. Da C ++ über eine explizite Speicherverwaltung verfügt, müssen Sie herausfinden, wann jedes Objekt freigegeben werden muss. Mit GC ist es nicht schlimmer; Vielmehr beschränkt sich das Problem darauf, herauszufinden, wann bestimmte Objekte freigegeben werden, nämlich diejenigen Objekte, die beim Löschen besondere Überlegungen erfordern. Die Erfahrung mit der Programmierung in Java und C # zeigt, dass die überwiegende Mehrheit der Objekte keine besonderen Überlegungen erfordert und sicher dem GC überlassen werden kann. Wie sich herausstellt, besteht eine der Hauptfunktionen von Destruktoren in C ++ darin, untergeordnete Objekte freizugeben, die GC automatisch für Sie verarbeitet.
Nate CK

2
@ NateC-K: Eine Sache, die in GC gegenüber Nicht-GC verbessert wurde (vielleicht die größte Sache), ist die Fähigkeit eines soliden GC-Systems, zu gewährleisten, dass jede Referenz weiterhin auf dasselbe Objekt zeigt, solange die Referenz existiert. Das Aufrufen Disposeeines Objekts kann dazu führen, dass es nicht mehr aktiviert werden kann. Verweise, die auf das Objekt zeigten, als es noch lebte, werden dies jedoch auch nach dem Tod tun. Im Gegensatz dazu können in Nicht-GC-Systemen Objekte gelöscht werden, solange Referenzen vorhanden sind, und es gibt selten eine Grenze für das Chaos, das entstehen kann, wenn eine dieser Referenzen verwendet wird.
Supercat

22

Obwohl dies ein alter ist Frage ist, gibt es immer noch ein Problem, das ich überhaupt nicht angesprochen habe: Es ist fast unmöglich, die Speicherbereinigung anzugeben.

Insbesondere achtet der C ++ - Standard sehr darauf, die Sprache in Bezug auf extern beobachtbares Verhalten anzugeben, anstatt wie die Implementierung dieses Verhalten erreicht. Bei der Speicherbereinigung gibt es jedoch praktisch kein extern beobachtbares Verhalten.

Die allgemeine Idee der Speicherbereinigung besteht darin, einen vernünftigen Versuch zu unternehmen, um sicherzustellen, dass eine Speicherzuweisung erfolgreich ist. Leider ist es im Wesentlichen unmöglich zu garantieren, dass eine Speicherzuweisung erfolgreich ist, selbst wenn Sie einen Garbage Collector in Betrieb haben. Dies trifft in gewissem Maße auf jeden Fall zu, insbesondere aber auf C ++, da es (wahrscheinlich) nicht möglich ist, einen Kopierkollektor (oder ähnliches) zu verwenden, der Objekte während eines Erfassungszyklus im Speicher verschiebt.

Wenn Sie Objekte nicht verschieben können, können Sie keinen einzigen zusammenhängenden Speicherbereich erstellen, aus dem Sie Ihre Zuweisungen vornehmen können. Dies bedeutet, dass Ihr Heap (oder Ihr freier Speicher oder wie auch immer Sie ihn nennen möchten) dies kann und wahrscheinlich auch wird werden im Laufe der Zeit fragmentiert. Dies kann wiederum verhindern, dass eine Zuordnung erfolgreich ist, selbst wenn mehr Speicher frei ist als der angeforderte Betrag.

Während es möglich sein könnte , mit kommen einige Garantie , dass (im Wesentlichen) sagt , dass , wenn Sie genau das gleiche Muster der Zuordnung immer wieder wiederholen, und es gelang zum ersten Mal, wird es weiterhin auf nachfolgende Iterationen erfolgreich zu sein, vorausgesetzt , dass der zugewiesene Speicher wurde zwischen den Iterationen unzugänglich. Das ist eine so schwache Garantie, dass sie im Wesentlichen nutzlos ist, aber ich sehe keine vernünftige Hoffnung, sie zu stärken.

Trotzdem ist es stärker als das, was für C ++ vorgeschlagen wurde. Der vorherige Vorschlag [Warnung: PDF] (der fallengelassen wurde) garantierte überhaupt nichts. Auf 28 Seiten des Vorschlags haben Sie einem extern beobachtbaren Verhalten eine einzige (nicht normative) Notiz in den Weg gestellt, die besagt:

[Hinweis: Bei durch Müll gesammelten Programmen sollte eine gehostete Implementierung von hoher Qualität versuchen, die Menge des nicht erreichbaren Speichers zu maximieren, den sie zurückfordert. - Endnote]

Zumindest für mich wirft dies eine ernsthafte Frage nach der Kapitalrendite auf. Wir werden vorhandenen Code brechen (niemand weiß genau, wie viel, aber definitiv einiges), neue Anforderungen an Implementierungen und neue Einschränkungen für Code stellen, und was wir dafür erhalten, ist möglicherweise überhaupt nichts?

Selbst im besten Fall erhalten wir Programme, die basierend auf Tests mit Java wahrscheinlich etwa sechsmal so viel Speicher benötigen, um mit der gleichen Geschwindigkeit ausgeführt zu werden, die sie jetzt ausführen. Schlimmer noch, die Garbage Collection war von Anfang an Teil von Java - C ++ schränkt den Garbage Collector so stark ein, dass er mit ziemlicher Sicherheit ein noch schlechteres Kosten-Nutzen-Verhältnis aufweist (selbst wenn wir über das hinausgehen, was der Vorschlag garantiert, und davon ausgehen, dass dies der Fall ist einige Vorteile).

Ich würde die Situation mathematisch zusammenfassen: Dies ist eine komplexe Situation. Wie jeder Mathematiker weiß, besteht eine komplexe Zahl aus zwei Teilen: real und imaginär. Es scheint mir, dass wir hier Kosten haben, die real sind, aber Vorteile, die (zumindest meistens) imaginär sind.


Ich würde postulieren , dass selbst wenn man an, der für den ordnungsgemäßen Betrieb alle Objekte gelöscht werden müssen, und nur Objekte , die für die Sammlung, Compiler berechtigt wäre gelöscht worden war Unterstützung für Referenz-Tracking Garbage Collection könnte noch nützlich sein, da eine solche Sprache gewährleisten könnte Die Verwendung eines gelöschten Zeigers (Referenz) würde garantiert einfangen, anstatt ein undefiniertes Verhalten zu verursachen.
Supercat

2
Selbst in Java ist der GC nicht wirklich spezifiziert, um irgendetwas Nützliches AFAIK zu tun. Es könnte freefür Sie freeerforderlich sein (wo ich analog zur C-Sprache meine ). Java garantiert jedoch niemals, Finalizer oder ähnliches aufzurufen. Tatsächlich kann C ++ viel mehr als Java ausführen, um Schreibvorgänge für Festschreibungsdatenbanken, das Löschen von Dateihandles usw. auszuführen. Java behauptet, "GC" zu haben, aber Java-Entwickler müssen close()ständig akribisch anrufen und sich des Ressourcenmanagements sehr bewusst sein, wobei darauf zu achten ist, dass sie nicht close()zu früh oder zu spät anrufen . C ++ befreit uns davon. ... (Fortsetzung)
Aaron McDaid

2
.. mein Kommentar vor einem Moment ist nicht dazu gedacht, Java zu kritisieren. Ich beobachte nur, dass der Begriff "Müllabfuhr" ein sehr seltsamer Begriff ist - er bedeutet viel weniger als die Leute denken und daher ist es schwierig, darüber zu diskutieren, ohne klar zu sein, was er bedeutet.
Aaron McDaid

@AaronMcDaid Es ist wahr, dass GC bei Nicht-Speicherressourcen überhaupt nicht hilft. Glücklicherweise werden solche Ressourcen im Vergleich zum Speicher ziemlich selten zugewiesen. Darüber hinaus können mehr als 90% von ihnen in der Methode freigegeben werden, die sie zugewiesen hat. try (Whatever w=...) {...}Lösen Sie sie daher (und Sie erhalten eine Warnung, wenn Sie sie vergessen). Die übrigen sind auch bei RAII problematisch. Der Aufruf close()„ die ganze Zeit“ bedeutet vielleicht einmal pro zehntausend Linien, das ist so nicht so schlecht, während der Speicher fast auf jeder Java - Linie zugeordnet wird.
Maaartinus

15

Wenn Sie eine automatische Speicherbereinigung wünschen, gibt es gute kommerzielle und gemeinfreie Speicherbereinigungsprogramme für C ++. Für Anwendungen, bei denen die Speicherbereinigung geeignet ist, ist C ++ eine hervorragende Sprache für die Speicherbereinigung mit einer Leistung, die im Vergleich zu anderen Sprachen für die Speicherbereinigung günstig ist. Unter Die Programmiersprache C ++ (4. Ausgabe) finden Sie eine Erläuterung der automatischen Speicherbereinigung in C ++. Siehe auch Hans-J. Böhms Site für C- und C ++ - Garbage Collection ( Archiv ).

Außerdem unterstützt C ++ Programmiertechniken, mit denen die Speicherverwaltung ohne Garbage Collector sicher und implizit ist . Ich betrachte die Speicherbereinigung als letzte Wahl und als unvollständige Methode für das Ressourcenmanagement. Das bedeutet nicht, dass es niemals nützlich ist, nur dass es in vielen Situationen bessere Ansätze gibt.

Quelle: http://www.stroustrup.com/bs_faq.html#garbage-collection

Wenn ich mich richtig erinnere, wurde es erfunden, bevor GC das Ding war , und ich glaube nicht, dass die Sprache aus mehreren Gründen GC haben könnte (IE-Abwärtskompatibilität mit C)

Hoffe das hilft.


"mit einer Leistung, die im Vergleich zu anderen Sprachen, die mit Müll gesammelt wurden, günstig ist". Zitat?
Jon Harrop

1
Mein Link war kaputt. Ich habe diese Antwort vor 5 Jahren geschrieben.
Rayne

1
Ok, ich hatte auf eine unabhängige Überprüfung dieser Behauptungen gehofft, dh nicht durch Stroustrup oder Boehm. :-)
Jon Harrop

12

Stroustrup hat dies auf der Going Native-Konferenz 2013 einige gute Kommentare abgegeben.

Springe in diesem Video einfach zu ungefähr 25m50s . (Ich würde empfehlen, das gesamte Video anzuschauen, aber dies geht zu den Informationen über die Speicherbereinigung über.)

Wenn Sie eine wirklich großartige Sprache haben, die es einfach (und sicher, vorhersehbar, leicht zu lesen und leicht zu lehren) macht, direkt mit Objekten und Werten umzugehen, und die (explizite) Verwendung der Sprache vermeidet Haufen, dann brauchen Sie nicht einmal wollen Garbage collection.

Mit modernem C ++ und dem, was wir in C ++ 11 haben, ist die Speicherbereinigung nur unter bestimmten Umständen mehr wünschenswert. Selbst wenn ein guter Garbage Collector in einen der wichtigsten C ++ - Compiler integriert ist, wird er meiner Meinung nach nicht sehr oft verwendet. Es wird einfacher und nicht schwieriger sein, den GC zu vermeiden.

Er zeigt dieses Beispiel:

void f(int n, int x) {
    Gadget *p = new Gadget{n};
    if(x<100) throw SomeException{};
    if(x<200) return;
    delete p;
}

Dies ist in C ++ nicht sicher. Aber es ist auch in Java unsicher! Wenn die Funktion in C ++ vorzeitig zurückkehrt, deletewird sie niemals aufgerufen. Wenn Sie jedoch über eine vollständige Speicherbereinigung verfügen, z. B. in Java, erhalten Sie lediglich den Vorschlag, dass das Objekt "irgendwann in der Zukunft" zerstört wird ( Update: Es ist noch schlimmer, dass dies der Fall ist. Java tut dies nichtversprechen, den Finalizer jemals anzurufen - er wird vielleicht nie aufgerufen). Dies ist nicht gut genug, wenn Gadget ein offenes Dateihandle oder eine Verbindung zu einer Datenbank oder Daten enthält, die Sie zum späteren Schreiben in eine Datenbank gepuffert haben. Wir möchten, dass das Gadget zerstört wird, sobald es fertig ist, um diese Ressourcen so schnell wie möglich freizugeben. Sie möchten nicht, dass Ihr Datenbankserver mit Tausenden von Datenbankverbindungen zu kämpfen hat, die nicht mehr benötigt werden. Er weiß nicht, dass Ihr Programm nicht mehr funktioniert.

Was ist die Lösung? Es gibt einige Ansätze. Der offensichtliche Ansatz, den Sie für die überwiegende Mehrheit Ihrer Objekte verwenden, ist:

void f(int n, int x) {
    Gadget p = {n};  // Just leave it on the stack (where it belongs!)
    if(x<100) throw SomeException{};
    if(x<200) return;
}

Die Eingabe erfordert weniger Zeichen. Es muss nicht newim Weg stehen. Sie müssen nicht Gadgetzweimal eingeben. Das Objekt wird am Ende der Funktion zerstört. Wenn Sie dies möchten, ist dies sehr intuitiv. Gadgets verhalten sich genauso wie intoder double. Vorhersehbar, leicht zu lesen, leicht zu lehren. Alles ist ein "Wert". Manchmal ein großer Wert, aber Werte sind einfacher zu vermitteln, weil Sie diese "Aktion auf Distanz" nicht haben, die Sie mit Zeigern (oder Referenzen) erhalten.

Die meisten von Ihnen erstellten Objekte sind nur für die Funktion vorgesehen, mit der sie erstellt wurden, und werden möglicherweise als Eingaben an untergeordnete Funktionen übergeben. Der Programmierer sollte nicht an die Speicherverwaltung denken müssen, wenn er Objekte zurückgibt oder Objekte auf andere Weise für weit voneinander entfernte Teile der Software freigibt.

Umfang und Lebensdauer sind wichtig. Meistens ist es einfacher, wenn die Lebensdauer dem Umfang entspricht. Es ist leichter zu verstehen und leichter zu lehren. Wenn Sie eine andere Lebensdauer wünschen, sollte es offensichtlich sein, dass Sie den Code lesen, den Sie tun, indem Sie shared_ptrbeispielsweise verwenden. (Oder (große) Objekte nach Wert zurückgeben, Bewegungssemantik nutzen oder unique_ptr.

Dies scheint ein Effizienzproblem zu sein. Was ist, wenn ich ein Gadget zurückgeben möchte foo()? Die Verschiebungssemantik von C ++ 11 erleichtert die Rückgabe großer Objekte. Schreiben Gadget foo() { ... }Sie einfach und es wird einfach funktionieren und schnell funktionieren. Sie müssen sich nicht mit sich &&selbst anlegen, sondern nur die Dinge nach Wert zurückgeben, und die Sprache kann häufig die erforderlichen Optimierungen vornehmen. (Schon vor C ++ 03 haben Compiler bemerkenswert gute Arbeit geleistet, um unnötiges Kopieren zu vermeiden.)

Wie Stroustrup an anderer Stelle im Video sagte (paraphrasiert): "Nur ein Informatiker würde darauf bestehen, ein Objekt zu kopieren und dann das Original zu zerstören. (Publikum lacht). Warum nicht einfach das Objekt direkt an den neuen Ort bewegen? Das ist, was Menschen (keine Informatiker) erwarten. "

Wenn Sie garantieren können, dass nur eine Kopie eines Objekts benötigt wird, ist es viel einfacher, die Lebensdauer des Objekts zu verstehen. Sie können auswählen, welche Lebenszeitrichtlinie Sie möchten, und die Speicherbereinigung ist vorhanden, wenn Sie möchten. Wenn Sie jedoch die Vorteile der anderen Ansätze verstehen, werden Sie feststellen, dass die Speicherbereinigung ganz unten in Ihrer Liste der Einstellungen steht.

Wenn das bei Ihnen nicht funktioniert, können Sie es verwenden unique_ptroder fehlschlagen shared_ptr. Gut geschriebenes C ++ 11 ist kürzer, leichter zu lesen und leichter zu unterrichten als viele andere Sprachen, wenn es um Speicherverwaltung geht.


1
GC sollte nur für Objekte verwendet werden, die keine Ressourcen erwerben (dh andere Entitäten bitten, "bis auf weiteres" Dinge in ihrem Namen zu tun). Wenn Sie Gadgetnichts anderes bitten, etwas in seinem Namen zu tun, wäre der ursprüngliche Code in Java absolut sicher, wenn die bedeutungslose (für Java) deleteAnweisung entfernt würde.
Supercat

@supercat, Objekte mit langweiligen Destruktoren sind interessant. (Ich habe nicht 'langweilig' definiert, sondern im Grunde genommen Destruktoren, die nie aufgerufen werden müssen, außer für die Freigabe von Speicher). Es kann für einen einzelnen Compiler möglich sein, shared_ptr<T>speziell zu behandeln, wenn Tes "langweilig" ist. Es könnte sich entscheiden, keinen Referenzzähler für diesen Typ zu verwalten und stattdessen GC zu verwenden. Dies würde die Verwendung von GC ermöglichen, ohne dass der Entwickler dies bemerken muss. A shared_ptrkönnte einfach als geeigneter GC-Zeiger angesehen werden T. Dies weist jedoch Einschränkungen auf, die viele Programme verlangsamen würden.
Aaron McDaid

Ein gutes Typsystem sollte unterschiedliche Typen für GC- und RAII-verwaltete Heap-Objekte haben, da einige Verwendungsmuster bei einem sehr gut und bei dem anderen sehr schlecht funktionieren. In .NET oder Java wird eine Anweisung string1=string2;unabhängig von der Länge der Zeichenfolge sehr schnell ausgeführt (es ist buchstäblich nichts anderes als ein Registerladen und ein Registerspeicher) und erfordert keine Sperre, um sicherzustellen, dass die obige Anweisung ausgeführt wird, während sie ausgeführt string2wird string1Wird geschrieben, wird entweder der alte oder der neue Wert ohne undefiniertes Verhalten gespeichert.
Supercat

In C ++ shared_ptr<String>erfordert die Zuweisung von a viel Synchronisation hinter den Kulissen, und die Zuweisung von a Stringkann sich merkwürdig verhalten, wenn eine Variable gleichzeitig gelesen und geschrieben wird. Fälle, in denen man Stringgleichzeitig schreiben und lesen möchte, sind nicht besonders häufig, können jedoch auftreten, wenn beispielsweise ein Code fortlaufende Statusberichte anderen Threads zur Verfügung stellen möchte. In .NET und Java "funktionieren" solche Dinge einfach.
Supercat

1
@curiousguy nichts hat sich geändert, es sei denn, Sie treffen die richtigen Vorsichtsmaßnahmen. Java ermöglicht es weiterhin, den Finalizer aufzurufen, sobald der Konstruktor fertig ist. Hier ein Beispiel aus dem wirklichen Leben: " finalize () hat stark erreichbare Objekte in Java 8 aufgerufen ". Die Schlussfolgerung ist, diese Funktion niemals zu verwenden, da fast jeder zustimmt, ein historischer Designfehler der Sprache zu sein. Wenn wir diesem Rat folgen, liefert die Sprache den Determinismus, den wir lieben.
Holger

11

Weil modernes C ++ keine Speicherbereinigung benötigt.

Die FAQ- Antwort von Bjarne Stroustrup zu diesem Thema lautet :

Ich mag keinen Müll. Ich mag keinen Müll. Mein Ideal ist es, die Notwendigkeit eines Müllsammlers zu beseitigen, indem ich keinen Müll produziere. Das ist jetzt möglich.


Die Situation für Code, der heutzutage geschrieben wird (C ++ 17 und gemäß den offiziellen Kernrichtlinien ), ist wie folgt:

  • Der meiste Code im Zusammenhang mit dem Speicherbesitz befindet sich in Bibliotheken (insbesondere in solchen, die Container bereitstellen).
  • Die meiste Verwendung von Code mit Speicherbesitz folgt dem RAII- Muster , sodass die Zuweisung bei der Konstruktion und die Freigabe bei der Zerstörung erfolgt, was beim Verlassen des Bereichs geschieht, in dem etwas zugewiesen wurde.
  • Sie weisen den Speicher nicht explizit direkt zu oder geben ihn frei .
  • Raw-Zeiger besitzen keinen Speicher (wenn Sie die Richtlinien befolgt haben), sodass Sie keine Leckagen verursachen können, indem Sie sie weitergeben.
  • Wenn Sie sich fragen, wie Sie die Startadressen von Wertesequenzen im Speicher übergeben sollen, tun Sie dies mit einer Zeitspanne . Kein roher Zeiger erforderlich.
  • Wenn Sie wirklich einen eigenen "Zeiger" benötigen, verwenden Sie die intelligenten Zeiger der Standardbibliothek von C ++ - sie können nicht auslaufen und sind anständig effizient (obwohl der ABI dem im Wege stehen kann ). Alternativ können Sie den Besitz mit "Eigentümerzeigern" über Bereichsgrenzen hinweg übergeben . Diese sind ungewöhnlich und müssen explizit verwendet werden. Aber wenn sie übernommen werden, ermöglichen sie eine gute statische Prüfung auf Undichtigkeiten.

"Oh ja? Aber was ist mit ...

... wenn ich nur Code schreibe, wie wir früher C ++ geschrieben haben? "

In der Tat könnten Sie einfach alle Richtlinien ignorieren und undichten Anwendungscode schreiben - und er wird wie immer kompiliert und ausgeführt (und leckt).

Aber es ist keine Situation, in der vom Entwickler erwartet wird, dass er tugendhaft ist und viel Selbstkontrolle ausübt. Es ist einfach nicht einfacher, nicht konformen Code zu schreiben, noch ist es schneller zu schreiben, noch ist es leistungsfähiger. Allmählich wird es auch schwieriger zu schreiben, da Sie mit einer zunehmenden "Impedanzfehlanpassung" konfrontiert werden, die dem entspricht, was konformer Code bietet und erwartet.

... wenn ich reintrepret_cast? Oder komplexe Zeigerarithmetik? Oder andere solche Hacks? "

In der Tat, wenn Sie sich dazu entschließen, können Sie Code schreiben, der die Dinge durcheinander bringt, obwohl Sie mit den Richtlinien gut spielen. Aber:

  1. Sie würden dies selten tun (in Bezug auf Stellen im Code, nicht unbedingt in Bezug auf den Bruchteil der Ausführungszeit).
  2. Sie würden dies nur absichtlich tun, nicht versehentlich.
  3. Dies wird in einer Codebasis hervorgehoben, die den Richtlinien entspricht.
  4. Es ist die Art von Code, in der Sie den GC sowieso in einer anderen Sprache umgehen würden.

... Bibliotheksentwicklung? "

Wenn Sie ein C ++ - Bibliotheksentwickler sind, schreiben Sie unsicheren Code mit rohen Zeigern, und Sie müssen sorgfältig und verantwortungsbewusst codieren. Dies sind jedoch in sich geschlossene Codeteile, die von Experten geschrieben (und vor allem von Experten überprüft) wurden.


Es ist also genau so, wie Bjarne gesagt hat: Es gibt wirklich keine Motivation, Müll im Allgemeinen zu sammeln, wie Sie alle, aber stellen Sie sicher, dass Sie keinen Müll produzieren. GC wird mit C ++ zu einem Problem.

Dies bedeutet nicht, dass GC für bestimmte Anwendungen kein interessantes Problem darstellt, wenn Sie benutzerdefinierte Zuordnungs- und Aufhebungsstrategien anwenden möchten. Für diejenigen, die eine benutzerdefinierte Zuweisung und Aufhebung der Zuweisung wünschen, keine GC auf Sprachebene.


Nun, es funktioniert (benötigt GC), wenn Sie Strings schleifen. Stellen Sie sich vor, Sie haben große String-Arrays (denken Sie an Hunderte von Megabyte), die Sie stückweise erstellen, dann verarbeiten und in verschiedene Längen umbauen, nicht verwendete löschen, andere kombinieren usw. I. weiß, weil ich auf Hochsprachen umsteigen musste, um damit fertig zu werden. (Natürlich können Sie auch Ihren eigenen GC erstellen).
www-0av-Com

2
@ user1863152: In diesem Fall wäre ein benutzerdefinierter Allokator hilfreich. Es erfordert immer noch keine
sprachintegrale

zu einpoklum: wahr. Es ist nur ein Pferd für Kurse. Meine Anforderung bestand darin, sich dynamisch ändernde Gallonen Transportpassagierinformationen zu verarbeiten. Faszinierendes Thema. Es kommt wirklich auf die Software-Philosophie an.
www-0av-Com

GC, wie die Java- und .NET-Welt herausgefunden hat, hat endlich ein massives Problem - es skaliert nicht. Wenn Sie Milliarden von lebenden Objekten im Speicher haben, wie wir es heutzutage mit einer nicht trivialen Software tun, müssen Sie Code schreiben, um Dinge vor dem GC zu verbergen. Es ist eine Belastung, GC in Java und .NET zu haben.
Zach Saw

10

Die Idee hinter C ++ war, dass Sie für Funktionen, die Sie nicht verwenden, keine Auswirkungen auf die Leistung haben. Das Hinzufügen der Garbage Collection hätte also bedeutet, dass einige Programme wie C direkt auf der Hardware ausgeführt werden und einige innerhalb einer virtuellen Laufzeitmaschine.

Nichts hindert Sie daran, intelligente Zeiger zu verwenden, die an einen Speicherbereinigungsmechanismus eines Drittanbieters gebunden sind. Ich erinnere mich an Microsoft, das so etwas mit COM gemacht hat, und es lief nicht gut.


2
Ich glaube nicht, dass GC eine VM benötigt. Der Compiler kann allen Zeigeroperationen Code hinzufügen, um einen globalen Status zu aktualisieren, während im Hintergrund ein separater Thread ausgeführt wird, der Objekte nach Bedarf löscht.
user83255

3
Genau. Sie brauchen keine virtuelle Maschine, aber sobald Sie anfangen, Ihren Speicher im Hintergrund so zu verwalten, habe ich das Gefühl, dass Sie die eigentlichen "elektrischen Kabel" verlassen haben und eine Art VM-Situation haben.
Uri


4

Eines der Grundprinzipien hinter der ursprünglichen C-Sprache ist, dass der Speicher aus einer Folge von Bytes besteht und der Code nur darauf achten muss, was diese Bytes genau zum Zeitpunkt ihrer Verwendung bedeuten. Modernes C ermöglicht es Compilern, zusätzliche Einschränkungen aufzuerlegen, aber C beinhaltet - und C ++ behält - die Möglichkeit, einen Zeiger in eine Folge von Bytes zu zerlegen, eine beliebige Folge von Bytes, die dieselben Werte enthalten, zu einem Zeiger zusammenzusetzen und diesen Zeiger dann zu verwenden Zugriff auf das frühere Objekt.

Während diese Fähigkeit in einigen Arten von Anwendungen nützlich oder sogar unverzichtbar sein kann, ist eine Sprache, die diese Fähigkeit enthält, in ihrer Fähigkeit, jede Art von nützlicher und zuverlässiger Speicherbereinigung zu unterstützen, sehr eingeschränkt. Wenn ein Compiler nicht alles weiß, was mit den Bits gemacht wurde, aus denen ein Zeiger besteht, kann er nicht wissen, ob Informationen, die zur Rekonstruktion des Zeigers ausreichen, möglicherweise irgendwo im Universum vorhanden sind. Da es möglich wäre, diese Informationen auf eine Weise zu speichern, auf die der Computer nicht zugreifen kann, selbst wenn er davon weiß (z. B. könnten die Bytes, aus denen der Zeiger besteht, lange genug auf dem Bildschirm angezeigt worden sein, damit jemand schreiben kann sie auf ein Stück Papier), kann es für einen Computer buchstäblich unmöglich sein zu wissen, ob ein Zeiger möglicherweise in der Zukunft verwendet werden könnte.

Eine interessante Besonderheit vieler durch Müll gesammelter Frameworks besteht darin, dass eine Objektreferenz nicht durch die darin enthaltenen Bitmuster definiert wird, sondern durch die Beziehung zwischen den in der Objektreferenz enthaltenen Bits und anderen an anderer Stelle enthaltenen Informationen. Wenn in C und C ++ das in einem Zeiger gespeicherte Bitmuster ein Objekt identifiziert, identifiziert dieses Bitmuster dieses Objekt, bis das Objekt explizit zerstört wird. In einem typischen GC-System kann ein Objekt zu einem bestimmten Zeitpunkt durch ein Bitmuster 0x1234ABCD dargestellt werden, aber der nächste GC-Zyklus kann alle Verweise auf 0x1234ABCD durch Verweise auf 0x4321BABE ersetzen, woraufhin das Objekt durch das letztere Muster dargestellt wird. Selbst wenn man das einer Objektreferenz zugeordnete Bitmuster anzeigen und später von der Tastatur zurücklesen würde,


Das ist ein wirklich guter Punkt, ich habe erst kürzlich ein paar Bits aus meinen Zeigern gestohlen, weil es sonst dumme Mengen an Cache-Fehlern geben würde.
Passant Bis zum

@PasserBy: Ich frage mich, wie viele Anwendungen, die 64-Bit-Zeiger verwenden, mehr davon profitieren würden, entweder skalierte 32-Bit-Zeiger als Objektreferenzen zu verwenden oder fast alles in 4 GB Adressraum zu belassen und spezielle Objekte zum Speichern / Abrufen von Daten von hoch zu verwenden -speedspeicher darüber hinaus? Maschinen haben genug RAM, so dass der RAM-Verbrauch von 64-Bit-Zeigern möglicherweise keine Rolle spielt, außer dass sie doppelt so viel Cache verschlingen wie 32-Bit-Zeiger.
Supercat

3

Alle technischen Gespräche machen das Konzept zu kompliziert.

Wenn Sie GC für den gesamten Speicher automatisch in C ++ einfügen, sollten Sie einen Webbrowser in Betracht ziehen. Der Webbrowser muss ein vollständiges Webdokument laden UND Web-Skripte ausführen. Sie können Web-Skript-Variablen im Dokumentbaum speichern. In einem BIG-Dokument in einem Browser mit vielen geöffneten Registerkarten bedeutet dies, dass der GC jedes Mal, wenn er eine vollständige Sammlung durchführen muss, auch alle Dokumentelemente scannen muss.

Auf den meisten Computern bedeutet dies, dass SEITENFEHLER auftreten. Der Hauptgrund für die Beantwortung der Frage ist also, dass SEITENFEHLER auftreten. Sie werden dies wissen, wenn Ihr PC anfängt, viel auf die Festplatte zuzugreifen. Dies liegt daran, dass der GC viel Speicher berühren muss, um ungültige Zeiger zu beweisen. Wenn Sie eine echte Anwendung haben, die viel Speicher verwendet, ist es aufgrund der SEITENFEHLER verheerend, alle Objekte jeder Sammlung scannen zu müssen. Ein Seitenfehler liegt vor, wenn der virtuelle Speicher von der Festplatte in den RAM zurückgelesen werden muss.

Die richtige Lösung besteht also darin, eine Anwendung in die Teile zu unterteilen, die GC benötigen, und die Teile, die dies nicht tun. Wenn im obigen Beispiel des Webbrowsers der Dokumentbaum mit malloc zugewiesen wurde, das Javascript jedoch mit GC ausgeführt wurde, durchsucht jedes Mal, wenn der GC eintritt, nur ein kleiner Teil des Speichers und alle PAGED OUT-Elemente des Speichers nach Der Dokumentbaum muss nicht erneut ausgelagert werden.

Um dieses Problem besser zu verstehen, schauen Sie sich den virtuellen Speicher und dessen Implementierung in Computern an. Es geht darum, dass dem Programm 2 GB zur Verfügung stehen, wenn nicht wirklich so viel RAM vorhanden ist. Auf modernen Computern mit 2 GB RAM für ein 32-BIt-System ist dies kein solches Problem, sofern nur ein Programm ausgeführt wird.

Betrachten Sie als zusätzliches Beispiel eine vollständige Sammlung, die alle Objekte verfolgen muss. Zuerst müssen Sie alle Objekte scannen, die über Wurzeln erreichbar sind. Scannen Sie anschließend alle in Schritt 1 sichtbaren Objekte. Scannen Sie dann wartende Destruktoren. Gehen Sie dann erneut zu allen Seiten und schalten Sie alle unsichtbaren Objekte aus. Dies bedeutet, dass viele Seiten möglicherweise mehrmals ausgetauscht werden.

Meine Antwort, um es kurz zu machen, ist, dass die Anzahl der SEITENFEHLER, die durch Berühren des gesamten Speichers auftreten, dazu führt, dass eine vollständige GC für alle Objekte in einem Programm nicht durchführbar ist. Daher muss der Programmierer die GC als Hilfe für Dinge wie Skripte betrachten und Datenbankarbeit, aber normale Dinge mit manueller Speicherverwaltung.

Und der andere sehr wichtige Grund sind natürlich globale Variablen. Damit der Kollektor weiß, dass sich ein globaler Variablenzeiger im GC befindet, wären bestimmte Schlüsselwörter erforderlich, und daher würde vorhandener C ++ - Code nicht funktionieren.


3

KURZE ANTWORT: Wir wissen nicht, wie wir die Speicherbereinigung effizient (mit geringem Zeit- und Platzaufwand) und immer korrekt (in allen möglichen Fällen) durchführen können.

LANGE ANTWORT: Genau wie C ist C ++ eine Systemsprache. Dies bedeutet, dass es verwendet wird, wenn Sie Systemcode schreiben, z. B. ein Betriebssystem. Mit anderen Worten, C ++ wurde genau wie C mit der bestmöglichen Leistung als Hauptziel entwickelt. Der Sprachstandard fügt keine Funktion hinzu, die das Leistungsziel beeinträchtigen könnte.

Dies wirft die Frage auf: Warum beeinträchtigt die Speicherbereinigung die Leistung? Der Hauptgrund ist, dass wir [Informatiker] bei der Implementierung nicht in allen Fällen wissen, wie man die Speicherbereinigung mit minimalem Aufwand durchführt. Daher ist es für den C ++ - Compiler und das Laufzeitsystem unmöglich, die Speicherbereinigung jederzeit effizient durchzuführen. Auf der anderen Seite sollte ein C ++ - Programmierer sein Design / seine Implementierung kennen und er ist die beste Person, um zu entscheiden, wie die Garbage Collection am besten durchgeführt werden soll.

Wenn Kontrolle (Hardware, Details usw.) und Leistung (Zeit, Raum, Leistung usw.) nicht die Hauptbeschränkungen sind, ist C ++ nicht das Schreibwerkzeug. Andere Sprachen sind möglicherweise besser geeignet und bieten mehr [versteckte] Laufzeitverwaltung mit dem erforderlichen Overhead.


3

Wenn wir C ++ mit Java vergleichen, sehen wir, dass C ++ nicht für implizite Garbage Collection entwickelt wurde, während Java dies tat.

Dinge wie beliebige Zeiger in C-Style zu haben, ist nicht nur schlecht für GC-Implementierungen, sondern würde auch die Abwärtskompatibilität für eine große Menge von C ++ - Legacy-Code zerstören.

Darüber hinaus ist C ++ eine Sprache, die als eigenständige ausführbare Datei ausgeführt werden soll, anstatt über eine komplexe Laufzeitumgebung zu verfügen.

Alles in allem: Ja, es ist möglicherweise möglich, Garbage Collection zu C ++ hinzuzufügen, aber aus Gründen der Kontinuität ist es besser, dies nicht zu tun.


1
Das Freigeben von Speicher und das Ausführen von Destruktoren sind zu völlig getrennte Probleme. (Java hat keine Destruktoren, was eine PITA ist.) GC gibt Speicher frei, es werden keine Dtoren ausgeführt.
Neugieriger

0

Hauptsächlich aus zwei Gründen:

  1. Weil es keinen braucht (IMHO)
  2. Weil es mit RAII, dem Eckpfeiler von C ++, so gut wie nicht kompatibel ist

C ++ bietet bereits manuelle Speicherverwaltung, Stapelzuweisung, RAII, Container, automatische Zeiger, intelligente Zeiger ... Das sollte ausreichen. Garbage Collectors sind für faule Programmierer gedacht, die nicht 5 Minuten darüber nachdenken möchten, wem welche Objekte gehören sollen oder wann Ressourcen freigegeben werden sollen. So machen wir das in C ++ nicht.


Es gibt zahlreiche (neuere) Algorithmen, die ohne Garbage Collection von Natur aus schwierig zu implementieren sind. Die Zeit verging. Innovation kommt auch von neuen Erkenntnissen, die gut zu (Müllsammel-) Hochsprachen passen. Versuchen Sie, eines dieser Elemente in GC-freies C ++ zurück zu portieren. Sie werden die Unebenheiten auf der Straße bemerken. (Ich weiß, ich sollte Beispiele nennen, aber ich habe es gerade eilig. Entschuldigung. Eine, an die ich jetzt denken kann, dreht sich um persistente Datenstrukturen, bei denen die Referenzzählung nicht funktioniert.)
BitTickler

0

Das Auferlegen einer Speicherbereinigung ist wirklich ein Paradigmenwechsel auf niedriger bis hoher Ebene.

Wenn Sie sich ansehen, wie Zeichenfolgen in einer Sprache mit Garbage Collection behandelt werden, werden Sie feststellen, dass sie NUR Funktionen zur Manipulation von Zeichenfolgen auf hoher Ebene zulassen und keinen binären Zugriff auf die Zeichenfolgen ermöglichen. Einfach ausgedrückt, überprüfen alle Zeichenfolgenfunktionen zuerst die Zeiger, um festzustellen, wo sich die Zeichenfolge befindet, auch wenn Sie nur ein Byte zeichnen. Wenn Sie also eine Schleife ausführen, die jedes Byte in einer Zeichenfolge in einer Sprache mit Garbage Collection verarbeitet, muss sie die Basisposition plus Offset für jede Iteration berechnen, da sie nicht wissen kann, wann die Zeichenfolge verschoben wurde. Dann muss man über Haufen, Stapel, Fäden usw. nachdenken.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.