Oder ist es eher so, als ob "das Entsorgen von Objekten in C ++ wirklich schwierig ist - ich verbringe 20% meiner Zeit damit und dennoch sind Speicherlecks ein häufiger Ort"?
Nach meiner persönlichen Erfahrung in C ++ und sogar C waren Speicherlecks nie ein großer Kampf, den man vermeiden konnte. Mit einem vernünftigen Testverfahren und Valgrind zum Beispiel wird jedes physische Leck, das durch einen Anruf operator new/malloc
ohne entsprechende verursacht delete/free
wird, oft schnell erkannt und behoben. Um fair zu sein, können einige große C ++ - oder C ++ - Codebasen der alten Schule sehr wahrscheinlich einige undurchsichtige Randfälle aufweisen, die hier und da einige Bytes Speicher physisch verlieren können, da dies nicht der deleting/freeing
Fall ist, der unter dem Radar des Testens geflogen ist.
Was die praktischen Beobachtungen angeht, sind die undichtesten Anwendungen, auf die ich stoße (wie bei Anwendungen, die immer mehr Speicher verbrauchen, je länger Sie sie ausführen, obwohl die Datenmenge, mit der wir arbeiten, nicht wächst), normalerweise nicht in C oder geschrieben C ++. Ich finde keine Dinge wie den Linux-Kernel oder die Unreal Engine oder sogar den nativen Code, der zur Implementierung von Java verwendet wird, in der Liste der undichten Software, auf die ich stoße.
Die bekannteste Art von undichter Software, auf die ich stoße, sind Dinge wie Flash-Applets, wie Flash-Spiele, obwohl sie die Speicherbereinigung verwenden. Und das ist kein fairer Vergleich, wenn man daraus etwas ableiten sollte, da viele Flash-Anwendungen von angehenden Entwicklern geschrieben wurden, denen wahrscheinlich solide technische Prinzipien und Testverfahren fehlen (und ich bin mir auch sicher, dass es qualifizierte Fachleute gibt, die mit GC arbeiten Kämpfe nicht mit undichter Software), aber ich würde jedem viel zu sagen haben, der glaubt, dass GC das Schreiben von undichter Software verhindert.
Baumelnde Zeiger
Aus meiner speziellen Domäne, Erfahrung und da ich hauptsächlich C und C ++ verwende (und ich gehe davon aus, dass die Vorteile von GC je nach unseren Erfahrungen und Bedürfnissen variieren werden), ist das Sofortigste, was GC für mich löst, nicht das Problem des praktischen Speicherverlusts baumelnder Zeigerzugriff, und das könnte in geschäftskritischen Szenarien buchstäblich ein Lebensretter sein.
Leider ersetzt GC in vielen Fällen, in denen ein ansonsten baumelnder Zeigerzugriff gelöst wird, dieselbe Art von Programmiererfehler durch einen logischen Speicherverlust.
Wenn Sie sich vorstellen, dass ein Flash-Spiel von einem angehenden Programmierer geschrieben wurde, speichert er möglicherweise Verweise auf Spielelemente in mehreren Datenstrukturen, sodass diese gemeinsam Eigentümer dieser Spielressourcen sind. Nehmen wir leider an, er macht einen Fehler, bei dem er vergessen hat, die Spielelemente aus einer der Datenstrukturen zu entfernen, als er zur nächsten Stufe überging, um zu verhindern, dass sie freigegeben werden, bis das gesamte Spiel beendet ist. Das Spiel scheint jedoch immer noch gut zu funktionieren, da die Elemente nicht gezeichnet werden oder die Benutzerinteraktion beeinträchtigen. Trotzdem verbraucht das Spiel immer mehr Speicher, während sich die Frameraten selbst zu einer Diashow entwickeln, während die versteckte Verarbeitung immer noch diese versteckte Sammlung von Elementen im Spiel durchläuft (deren Größe inzwischen explosiv geworden ist). Dies ist das Problem, auf das ich in solchen Flash-Spielen häufig stoße.
- Ich habe Leute getroffen, die sagten, dass dies nicht als "Speicherverlust" gilt, da der Speicher beim Schließen der Anwendung immer noch freigegeben wird und stattdessen als "Speicherverlust" oder ähnliches bezeichnet werden könnte. Während eine solche Unterscheidung nützlich sein könnte, um Probleme zu identifizieren und darüber zu sprechen, finde ich solche Unterscheidungen in diesem Zusammenhang nicht so nützlich, wenn wir darüber sprechen, als wäre sie nicht so problematisch wie ein "Speicherverlust", wenn wir es tun Das praktische Ziel, sicherzustellen, dass die Software nicht so viel Speicherplatz beansprucht, je länger wir sie ausführen (es sei denn, es handelt sich um obskure Betriebssysteme, die den Speicher eines Prozesses beim Beenden nicht freigeben).
Nehmen wir nun an, derselbe angehende Entwickler hat das Spiel in C ++ geschrieben. In diesem Fall gibt es normalerweise nur eine zentrale Datenstruktur im Spiel, die den Speicher "besitzt", während andere auf diesen Speicher verweisen. Wenn er den gleichen Fehler macht, besteht die Möglichkeit, dass das Spiel beim Übergang zur nächsten Stufe aufgrund des Zugriffs auf baumelnde Zeiger abstürzt (oder schlimmer noch, etwas anderes als Absturz).
Dies ist die unmittelbarste Art von Kompromiss, den ich in meiner Domäne am häufigsten zwischen GC und keinem GC finde. Und ich interessiere mich eigentlich nicht sehr für GC in meiner Domäne, was nicht sehr geschäftskritisch ist, da die größten Probleme, die ich jemals mit undichter Software hatte, die zufällige Verwendung von GC in einem ehemaligen Team waren, die die oben beschriebenen Lecks verursachte .
In meiner speziellen Domäne bevorzuge ich es in vielen Fällen, dass die Software abstürzt oder ausfällt, da dies zumindest viel einfacher zu erkennen ist, als herauszufinden, warum die Software auf mysteriöse Weise explosive Speichermengen verbraucht, nachdem sie eine halbe Stunde lang ausgeführt wurde Geräte- und Integrationstests bestehen ohne Beanstandung (nicht einmal von Valgrind, da der Speicher beim Herunterfahren von GC freigegeben wird). Dies ist jedoch kein Slam für GC von meiner Seite oder ein Versuch zu sagen, dass es nutzlos ist oder so etwas, aber es war in den Teams, mit denen ich gegen undichte Software zusammengearbeitet habe, keine Silberkugel, auch nicht in der Nähe im Gegenteil, ich hatte die gegenteilige Erfahrung mit dieser einen Codebasis, bei der GC die leckeste ist, die mir jemals begegnet ist. Um fair zu sein, wussten viele Mitglieder dieses Teams nicht einmal, was schwache Referenzen waren.
Shared Ownership und Psychologie
Das Problem, das ich bei der Speicherbereinigung finde, die es so anfällig für "Speicherlecks" machen kann (und ich werde darauf bestehen, es als solches zu bezeichnen, wie das "Speicherleck" sich aus Sicht des Benutzers genauso verhält), liegt in den Händen von Diejenigen, die es nicht mit Sorgfalt verwenden, beziehen sich meiner Erfahrung nach zu einem gewissen Grad auf "menschliche Tendenzen". Das Problem mit diesem Team und der undichtesten Codebasis, auf die ich jemals gestoßen bin, war, dass sie den Eindruck hatten, dass GC es ihnen ermöglichen würde, nicht mehr darüber nachzudenken, wem Ressourcen gehören.
In unserem Fall hatten wir so viele Objekte, die sich gegenseitig referenzierten. Modelle verweisen zusammen mit der Materialbibliothek und dem Shader-System auf Materialien. Materialien verweisen auf Texturen zusammen mit der Texturbibliothek und bestimmten Shadern. Kameras speichern Verweise auf alle Arten von Szenenelementen, die vom Rendern ausgeschlossen werden sollten. Die Liste schien auf unbestimmte Zeit weiterzugehen. Dies führte dazu, dass fast jede umfangreiche Ressource im System an mehr als 10 anderen Stellen im Anwendungsstatus gleichzeitig besessen und auf Lebenszeit erweitert wurde, und das war sehr, sehr anfällig für menschliches Versagen, das sich in einem Leck niederschlug (und nicht) Ich spreche von Gigabyte in Minuten mit schwerwiegenden Usability-Problemen. Konzeptionell mussten alle diese Ressourcen nicht im Eigentum geteilt werden, sie hatten konzeptionell alle einen Eigentümer.
Wenn wir aufhören, darüber nachzudenken, wem welcher Speicher gehört, und glücklicherweise nur lebenslange Verweise auf Objekte überall speichern, ohne darüber nachzudenken, stürzt die Software nicht aufgrund von baumelnden Zeigern ab, sondern mit ziemlicher Sicherheit unter solchen Bedingungen Nachlässige Denkweise, fangen Sie an, Speicher wie verrückt auf eine Weise zu verlieren, die sehr schwer aufzuspüren ist und Tests entgeht.
Wenn der baumelnde Zeiger in meiner Domäne einen praktischen Vorteil hat, ist dies, dass er sehr schlimme Störungen und Abstürze verursacht. Und das gibt den Entwicklern zumindest den Anreiz, wenn sie etwas Zuverlässiges versenden möchten, über Ressourcenmanagement nachzudenken und die richtigen Maßnahmen zu ergreifen, um alle zusätzlichen Verweise / Zeiger auf ein Objekt zu entfernen, das konzeptionell nicht mehr benötigt wird.
Verwaltung von Anwendungsressourcen
Richtiges Ressourcenmanagement ist der Name des Spiels, wenn es darum geht, Lecks in langlebigen Anwendungen zu vermeiden, bei denen ein dauerhafter Zustand gespeichert wird, in dem Leckagen schwerwiegende Probleme mit der Framerate und der Benutzerfreundlichkeit verursachen würden. Und die korrekte Verwaltung von Ressourcen ist hier mit oder ohne GC nicht weniger schwierig. Die Arbeit ist nicht weniger manuell, um die entsprechenden Verweise auf nicht mehr benötigte Objekte zu entfernen, unabhängig davon, ob es sich um Zeiger oder lebenslange Referenzen handelt.
Das ist die Herausforderung in meinem Bereich, nicht zu vergessen, delete
was wir tun new
(es sei denn, wir sprechen von einer Amateurstunde mit schlechten Tests, Praktiken und Werkzeugen). Und es erfordert Nachdenken und Sorgfalt, ob wir GC verwenden oder nicht.
Multithreading
Das andere Problem, das ich bei GC sehr nützlich finde, wenn es in meiner Domäne sehr vorsichtig verwendet werden kann, ist die Vereinfachung der Ressourcenverwaltung in Multithreading-Kontexten. Wenn wir darauf achten, lebenslange Verlängerungsreferenzen auf Ressourcen nicht an mehr als einer Stelle im Anwendungsstatus zu speichern, kann die lebenslange Verlängerung von GC-Referenzen äußerst nützlich sein, damit Threads eine Ressource, auf die zugegriffen wird, vorübergehend erweitern können, um sie zu erweitern Die Lebensdauer beträgt nur kurze Zeit, damit der Thread die Verarbeitung abschließen kann.
Ich denke, dass eine sehr sorgfältige Verwendung von GC auf diese Weise zu einer sehr korrekten Software führen kann, die nicht undicht ist und gleichzeitig das Multithreading vereinfacht.
Es gibt Möglichkeiten, dies zu umgehen, obwohl keine GC vorhanden ist. In meinem Fall vereinheitlichen wir die Darstellung der Szenenentität der Software mit Threads, die vorübergehend dazu führen, dass die Szenenressourcen vor einer Bereinigungsphase für kurze Zeit auf eine eher allgemeine Weise erweitert werden. Dies mag ein bisschen nach GC riechen, aber der Unterschied besteht darin, dass es sich nicht um ein "gemeinsames Eigentum" handelt, sondern nur um ein einheitliches Szenenverarbeitungsdesign in Threads, das die Zerstörung dieser Ressourcen verzögert. Dennoch wäre es viel einfacher, sich hier nur auf GC zu verlassen, wenn es für solche Multithreading-Fälle sehr sorgfältig mit gewissenhaften Entwicklern verwendet werden könnte, die darauf achten, schwache Referenzen in den relevanten persistenten Bereichen zu verwenden.
C ++
Schließlich:
In C ++ muss ich delete aufrufen, um ein erstelltes Objekt am Ende seines Lebenszyklus zu entsorgen.
In Modern C ++ sollten Sie dies im Allgemeinen nicht manuell tun. Es geht nicht einmal so sehr darum, zu vergessen, es zu tun. Wenn Sie die Ausnahmebehandlung in das Bild einbeziehen, kann, selbst wenn Sie einen entsprechenden delete
Aufruf an unten geschrieben haben new
, etwas in die Mitte werfen und den delete
Aufruf nie erreichen , wenn Sie sich nicht auf automatisierte Destruktoraufrufe verlassen, für die der Compiler dies einfügt Du.
Mit C ++ müssen Sie praktisch eine solche manuelle Ressourcenbereinigung vermeiden (dies schließt das Vermeiden manueller Aufrufe zum Entsperren eines Mutex außerhalb eines dtors ein, es sei denn, Sie arbeiten in einem eingebetteten Kontext mit deaktivierten Ausnahmen und speziellen Bibliotheken, die absichtlich so programmiert sind, dass sie nicht ausgelöst werden zB und nicht nur Speicherfreigabe). Die Ausnahmebehandlung erfordert dies ziemlich genau, daher sollte die gesamte Ressourcenbereinigung größtenteils über Destruktoren automatisiert werden.