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.
c++
garbage-collection
c++11
Jason Baker
quelle
quelle
Antworten:
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:
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.
quelle
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.std::shared_ptr
der Speicherbereinigung mit Referenzzählung (dh ) sind zyklische Referenzen, die einen Speicherverlust verursachen. Daher müssen Sie vorsichtig verwendenstd::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.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/…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 Beckett
das Problem zeigt, ist möglicherweise nicht die Leistung an sich, sondern die Vorhersagbarkeit der Leistung.Derzeit sind zwei GC-Familien weit verbreitet:
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 Counting
ist 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 dieMark And Sweep
.Ich habe ein Papier von Eiffel-Implementierern gesehen, die versuchten, einen
Reference Counting
Garbage Collector zu implementieren , der eine ähnliche globale Leistung wieMark And Sweep
ohne 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:Die Idee ist, die Lebensdauer des Objekts richtig zu steuern:
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:
using
Konstrukt ... 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 dasusing
Schlü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_ptr
undunique_ptr
verwenden 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 Richtung
shared_ptr
.Es ist großartig, dafür ist Boost schließlich, aber es ist keine Silberkugel. Tatsächlich besteht das Hauptproblem darin,
shared_ptr
dass es einen GC emuliert, der von implementiert wurde,Reference Counting
aber Sie müssen die Zykluserkennung selbst implementieren ... UrgNatürlich gibt es dieses
weak_ptr
Ding, aber ich habe leider bereits Speicherlecks gesehen, obwohlshared_ptr
diese 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:
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.
quelle
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!
quelle
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:
Dies sind nur einige der Probleme, mit denen wir konfrontiert sind.
quelle
Dispose
eines 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.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:
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.
quelle
free
für Siefree
erforderlich 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üssenclose()
ständig akribisch anrufen und sich des Ressourcenmanagements sehr bewusst sein, wobei darauf zu achten ist, dass sie nichtclose()
zu früh oder zu spät anrufen . C ++ befreit uns davon. ... (Fortsetzung)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 Aufrufclose()
„ 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.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.
quelle
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:
Dies ist in C ++ nicht sicher. Aber es ist auch in Java unsicher! Wenn die Funktion in C ++ vorzeitig zurückkehrt,
delete
wird 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:
Die Eingabe erfordert weniger Zeichen. Es muss nicht
new
im Weg stehen. Sie müssen nichtGadget
zweimal eingeben. Das Objekt wird am Ende der Funktion zerstört. Wenn Sie dies möchten, ist dies sehr intuitiv.Gadget
s verhalten sich genauso wieint
oderdouble
. 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_ptr
beispielsweise verwenden. (Oder (große) Objekte nach Wert zurückgeben, Bewegungssemantik nutzen oderunique_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. SchreibenGadget 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_ptr
oder fehlschlagenshared_ptr
. Gut geschriebenes C ++ 11 ist kürzer, leichter zu lesen und leichter zu unterrichten als viele andere Sprachen, wenn es um Speicherverwaltung geht.quelle
Gadget
nichts anderes bitten, etwas in seinem Namen zu tun, wäre der ursprüngliche Code in Java absolut sicher, wenn die bedeutungslose (für Java)delete
Anweisung entfernt würde.shared_ptr<T>
speziell zu behandeln, wennT
es "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. Ashared_ptr
könnte einfach als geeigneter GC-Zeiger angesehen werdenT
. Dies weist jedoch Einschränkungen auf, die viele Programme verlangsamen würden.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ührtstring2
wirdstring1
Wird geschrieben, wird entweder der alte oder der neue Wert ohne undefiniertes Verhalten gespeichert.shared_ptr<String>
erfordert die Zuweisung von a viel Synchronisation hinter den Kulissen, und die Zuweisung von aString
kann sich merkwürdig verhalten, wenn eine Variable gleichzeitig gelesen und geschrieben wird. Fälle, in denen manString
gleichzeitig 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.Weil modernes C ++ keine Speicherbereinigung benötigt.
Die FAQ- Antwort von Bjarne Stroustrup zu diesem Thema lautet :
Die Situation für Code, der heutzutage geschrieben wird (C ++ 17 und gemäß den offiziellen Kernrichtlinien ), ist wie folgt:
"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:
... 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.
quelle
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.
quelle
Um die meisten "Warum" -Fragen zu C ++ zu beantworten, lesen Sie Design und Evolution von C ++
quelle
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,
quelle
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.
quelle
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.
quelle
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.
quelle
Hauptsächlich aus zwei Gründen:
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.
quelle
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.
quelle