Was sind einige allgemeine Tipps, um sicherzustellen, dass in C ++ - Programmen kein Speicher verloren geht? Wie finde ich heraus, wer den dynamisch zugewiesenen Speicher freigeben soll?
c++
memory
memory-management
raii
Dulipishi
quelle
quelle
Antworten:
Versuchen Sie, den Speicher nicht manuell zu verwalten, sondern gegebenenfalls intelligente Zeiger zu verwenden.
Schauen Sie sich die Boost lib , TR1 und Smart Pointer an .
Auch intelligente Zeiger sind jetzt Teil des C ++ - Standards C ++ 11 .
quelle
Ich unterstütze alle Ratschläge zu RAII und intelligenten Zeigern gründlich, möchte aber auch einen etwas übergeordneten Tipp hinzufügen: Der am einfachsten zu verwaltende Speicher ist der Speicher, den Sie nie zugewiesen haben. Im Gegensatz zu Sprachen wie C # und Java, in denen so ziemlich alles eine Referenz ist, sollten Sie in C ++ Objekte auf den Stapel legen, wann immer Sie können. Wie ich gesehen habe, weisen mehrere Leute (einschließlich Dr. Stroustrup) darauf hin, dass der Hauptgrund, warum die Speicherbereinigung in C ++ nie populär war, darin besteht, dass gut geschriebenes C ++ überhaupt nicht viel Müll produziert.
Schreib nicht
oder auch
wenn du nur schreiben kannst
quelle
Verwenden Sie RAII
Dieser Beitrag scheint sich zu wiederholen, aber in C ++ ist RAII das grundlegendste Muster, das man kennen muss .
Erfahren Sie, wie Sie intelligente Zeiger verwenden, sowohl von Boost über TR1 als auch von Auto_ptr (aber häufig effizient genug) (aber Sie müssen die Einschränkungen kennen).
RAII ist die Grundlage sowohl für die Ausnahmesicherheit als auch für die Ressourcenentsorgung in C ++, und kein anderes Muster (Sandwich usw.) gibt Ihnen beides (und meistens gibt es Ihnen keines).
Unten sehen Sie einen Vergleich von RAII- und Nicht-RAII-Code:
Über RAII
Zusammenfassend (nach dem Kommentar von Ogre Psalm33 ) stützt sich RAII auf drei Konzepte:
Dies bedeutet, dass im richtigen C ++ - Code die meisten Objekte nicht mit erstellt
new
werden und stattdessen auf dem Stapel deklariert werden. Und für diejenigen, die mit konstruiert wurdennew
, sind alle irgendwie begrenzt (z. B. an einen intelligenten Zeiger angehängt).Als Entwickler ist dies in der Tat sehr leistungsfähig, da Sie sich nicht um die manuelle Handhabung von Ressourcen kümmern müssen (wie in C oder für einige Objekte in Java, die
try
/finally
für diesen Fall intensiv nutzen ) ...Bearbeiten (2012-02-12)
wilhelmtell hat damit recht: Es gibt außergewöhnliche Möglichkeiten, RAII zu betrügen, die alle dazu führen, dass der Prozess abrupt gestoppt wird.
Dies sind außergewöhnliche Möglichkeiten, da C ++ - Code nicht mit Beenden, Beenden usw. übersät ist. Im Falle von Ausnahmen möchten wir, dass eine nicht behandelte Ausnahme den Prozess zum Absturz bringt und das Speicherabbild unverändert und nicht nach der Bereinigung ausgibt .
Aber wir müssen immer noch über diese Fälle Bescheid wissen, denn obwohl sie selten auftreten, können sie dennoch auftreten.
(Wer ruft an
terminate
oderexit
in gelegentlichem C ++ - Code? ... Ich erinnere mich, dass ich mich beim Spielen mit GLUT mit diesem Problem befassen musste : Diese Bibliothek ist sehr C-orientiert und geht so weit, sie aktiv zu entwerfen, um es C ++ - Entwicklern zu erschweren, sich nicht darum zu kümmern über vom Stapel zugewiesene Daten oder über "interessante" Entscheidungen, niemals von ihrer Hauptschleife zurückzukehren ... dazu werde ich nichts sagen) .quelle
Sie sollten sich intelligente Zeiger ansehen, z. B. die intelligenten Zeiger von boost .
Anstatt
boost :: shared_ptr wird automatisch gelöscht, sobald der Referenzzähler Null ist:
Beachten Sie meine letzte Anmerkung: "Wenn die Referenzanzahl Null ist, ist dies der coolste Teil. Wenn Sie also mehrere Benutzer Ihres Objekts haben, müssen Sie nicht nachverfolgen, ob das Objekt noch verwendet wird. Sobald sich niemand mehr auf Ihr Objekt bezieht." geteilter Zeiger, es wird zerstört.
Dies ist jedoch kein Allheilmittel. Obwohl Sie auf den Basiszeiger zugreifen können, möchten Sie ihn nicht an eine Drittanbieter-API übergeben, es sei denn, Sie waren sich sicher, was er tat. Oftmals werden Ihre "Posting" -Stücke in einem anderen Thread veröffentlicht, damit die Arbeit erledigt werden kann, nachdem der Erstellungsbereich abgeschlossen ist. Dies ist bei PostThreadMessage in Win32 üblich:
Verwenden Sie wie immer Ihre Denkmütze mit jedem Werkzeug ...
quelle
Informieren Sie sich über RAII und stellen Sie sicher, dass Sie es verstehen.
quelle
Die meisten Speicherverluste sind darauf zurückzuführen, dass der Besitz und die Lebensdauer von Objekten nicht klar sind.
Das erste, was Sie tun müssen, ist, den Stapel zuzuweisen, wann immer Sie können. Dies betrifft die meisten Fälle, in denen Sie ein einzelnes Objekt für einen bestimmten Zweck zuweisen müssen.
Wenn Sie ein Objekt "neu" machen müssen, hat es die meiste Zeit für den Rest seines Lebens einen einzigen offensichtlichen Besitzer. In dieser Situation verwende ich in der Regel eine Reihe von Sammlungsvorlagen, die dazu bestimmt sind, Objekte zu besitzen, die per Zeiger in ihnen gespeichert sind. Sie werden mit dem STL-Vektor und den Kartencontainern implementiert, weisen jedoch einige Unterschiede auf:
Mein Vorteil bei STL ist, dass es sich so stark auf Wertobjekte konzentriert, während Objekte in den meisten Anwendungen eindeutige Entitäten sind, für deren Verwendung in diesen Containern keine aussagekräftige Kopiersemantik erforderlich ist.
quelle
Bah, du kleine Kinder und deine neuen Müllsammler ...
Sehr strenge Regeln zum "Eigentum" - welches Objekt oder welcher Teil der Software hat das Recht, das Objekt zu löschen. Klare Kommentare und kluge Variablennamen, um deutlich zu machen, ob ein Zeiger "besitzt" oder "nur schauen, nicht berühren" ist. Um zu entscheiden, wem was gehört, befolgen Sie so weit wie möglich das "Sandwich" -Muster in jeder Unterroutine oder Methode.
Manchmal ist es notwendig, an sehr unterschiedlichen Orten zu erschaffen und zu zerstören. Ich denke schwer, das zu vermeiden.
In jedem Programm, das komplexe Datenstrukturen erfordert, erstelle ich einen strengen Baum von Objekten, die andere Objekte enthalten - unter Verwendung von "Eigentümer" -Zeigern. Dieser Baum modelliert die grundlegende Hierarchie von Anwendungsdomänenkonzepten. Beispiel: Eine 3D-Szene besitzt Objekte, Lichter und Texturen. Am Ende des Renderns, wenn das Programm beendet wird, gibt es eine klare Möglichkeit, alles zu zerstören.
Viele andere Zeiger werden nach Bedarf definiert, wenn eine Entität Zugriff auf eine andere benötigt, um über Arays oder was auch immer zu scannen. das sind die "nur schauen". Für das Beispiel einer 3D-Szene verwendet ein Objekt eine Textur, besitzt diese jedoch nicht. Andere Objekte verwenden möglicherweise dieselbe Textur. Die Zerstörung eines Objekts führt nicht zur Zerstörung von Texturen.
Ja, es ist zeitaufwändig, aber genau das mache ich. Ich habe selten Speicherlecks oder andere Probleme. Aber dann arbeite ich auf dem begrenzten Gebiet der Hochleistungssoftware für Wissenschaft, Datenerfassung und Grafik. Ich beschäftige mich nicht oft mit Transaktionen wie Bank- und E-Commerce-Transaktionen, ereignisgesteuerten GUIs oder stark vernetztem asynchronem Chaos. Vielleicht haben die neuen Wege dort einen Vorteil!
quelle
Gute Frage!
Wenn Sie C ++ verwenden und eine Echtzeit-CPU- und Speicher-Boud-Anwendung (wie Spiele) entwickeln, müssen Sie Ihren eigenen Speichermanager schreiben.
Ich denke, das Bessere, was Sie tun können, ist, einige interessante Werke verschiedener Autoren zusammenzuführen. Ich kann Ihnen einen Hinweis geben:
Der Allokator mit fester Größe wird überall im Netz stark diskutiert
Small Object Allocation wurde 2001 von Alexandrescu in seinem perfekten Buch "Modern c ++ design" eingeführt.
Eine große Weiterentwicklung (mit verteiltem Quellcode) findet sich in einem erstaunlichen Artikel in Game Programming Gem 7 (2008) mit dem Titel "High Performance Heap Allocator" von Dimitar Lazarov
Eine große Liste von Ressourcen finden Sie in diesem Artikel
Schreiben Sie nicht selbst einen unbenutzenden Noob-Allokator ... DOKUMENTIEREN SIE SICH zuerst.
quelle
Eine Technik, die bei der Speicherverwaltung in C ++ populär geworden ist, ist RAII . Grundsätzlich verwenden Sie Konstruktoren / Destruktoren, um die Ressourcenzuweisung zu handhaben. Natürlich gibt es in C ++ aufgrund der Ausnahmesicherheit einige andere unangenehme Details, aber die Grundidee ist ziemlich einfach.
Das Problem hängt im Allgemeinen vom Eigentum ab. Ich empfehle dringend, die Effective C ++ - Reihe von Scott Meyers und Modern C ++ Design von Andrei Alexandrescu zu lesen.
quelle
Es gibt bereits eine Menge darüber, wie man keine Leckagen verursacht. Wenn Sie jedoch ein Tool benötigen, mit dem Sie Leckagen nachverfolgen können, schauen Sie sich Folgendes an:
quelle
Benutzer intelligente Zeiger überall, wo Sie können! Ganze Klassen von Speicherlecks verschwinden einfach.
quelle
Teilen und kennen Sie die Regeln für den Speicherbesitz in Ihrem Projekt. Die Verwendung der COM-Regeln sorgt für die beste Konsistenz ([in] -Parameter gehören dem Anrufer, Angerufene müssen kopieren; [out] -Parameter gehören dem Anrufer, Angerufene müssen eine Kopie erstellen, wenn eine Referenz aufbewahrt wird; usw.)
quelle
valgrind ist ein gutes Werkzeug, um Speicherverluste Ihres Programms auch zur Laufzeit zu überprüfen.
Es ist auf den meisten Linux-Versionen (einschließlich Android) und auf Darwin verfügbar.
Wenn Sie Unit-Tests für Ihre Programme schreiben, sollten Sie es sich zur Gewohnheit machen, Valgrind systematisch für Tests auszuführen. Dadurch werden möglicherweise frühzeitig viele Speicherverluste vermieden. Es ist normalerweise auch einfacher, sie in einfachen Tests zu lokalisieren, als in einer vollständigen Software.
Natürlich bleibt dieser Rat für jedes andere Tool zur Speicherprüfung gültig.
quelle
Verwenden Sie auch keinen manuell zugewiesenen Speicher, wenn eine Standardbibliotheksklasse (z. B. ein Vektor) vorhanden ist. Stellen Sie sicher, dass Sie einen virtuellen Destruktor haben, wenn Sie gegen diese Regel verstoßen.
quelle
Wenn Sie für etwas keinen intelligenten Zeiger verwenden können (obwohl dies eine große rote Fahne sein sollte), geben Sie Ihren Code ein mit:
Das ist offensichtlich, aber stellen Sie sicher, dass Sie es eingeben, bevor Sie Code in den Bereich eingeben
quelle
Eine häufige Ursache für diese Fehler ist, wenn Sie über eine Methode verfügen, die einen Verweis oder Zeiger auf ein Objekt akzeptiert, den Besitz jedoch unklar lässt. Stil- und Kommentarkonventionen können dies weniger wahrscheinlich machen.
Der Fall, in dem die Funktion das Eigentum an dem Objekt übernimmt, sei der Sonderfall. Schreiben Sie in allen Situationen, in denen dies geschieht, einen Kommentar neben die Funktion in die Header-Datei, der dies angibt. Sie sollten sich bemühen, sicherzustellen, dass in den meisten Fällen das Modul oder die Klasse, die ein Objekt zuweist, auch für die Freigabe verantwortlich ist.
Die Verwendung von const kann in einigen Fällen sehr hilfreich sein. Wenn eine Funktion ein Objekt nicht ändert und keinen Verweis darauf speichert, der nach seiner Rückkehr bestehen bleibt, akzeptieren Sie einen konstanten Verweis. Aus dem Lesen des Anrufercodes geht hervor, dass Ihre Funktion das Eigentum an dem Objekt nicht akzeptiert hat. Sie hätten dieselbe Funktion einen Nicht-Konstanten-Zeiger akzeptieren lassen können, und der Aufrufer könnte angenommen haben oder nicht, dass der Angerufene den Besitz angenommen hat, aber mit einer Konstanten-Referenz gibt es keine Frage.
Verwenden Sie keine nicht konstanten Referenzen in Argumentlisten. Beim Lesen des Anrufercodes ist sehr unklar, ob der Angerufene möglicherweise einen Verweis auf den Parameter beibehalten hat.
Ich bin mit den Kommentaren nicht einverstanden, in denen Zeiger mit Referenzzählung empfohlen werden. Dies funktioniert normalerweise gut, aber wenn Sie einen Fehler haben und es nicht funktioniert, insbesondere wenn Ihr Destruktor etwas nicht Triviales tut, wie in einem Multithread-Programm. Versuchen Sie auf jeden Fall, Ihr Design so anzupassen, dass keine Referenzzählung erforderlich ist, wenn es nicht zu schwierig ist.
quelle
Tipps in der Reihenfolge ihrer Wichtigkeit:
-Tipp Nr. 1 Denken Sie immer daran, Ihre Destruktoren als "virtuell" zu deklarieren.
-Tipp Nr. 2 Verwenden Sie RAII
-Tipp Nr. 3 Verwenden Sie die Smartpointers von Boost
-Tipp Nr. 4 Schreiben Sie keine eigenen fehlerhaften Smartpointers, verwenden Sie Boost (bei einem Projekt, an dem ich gerade arbeite, kann ich Boost nicht verwenden, und ich musste meine eigenen Smartpointer debuggen, die ich definitiv nicht nehmen würde wieder die gleiche Route, aber im Moment kann ich unseren Abhängigkeiten keinen Schub hinzufügen)
-Tipp Nr. 5 Wenn es um gelegentliche / nicht leistungskritische Arbeiten geht (wie bei Spielen mit Tausenden von Objekten), schauen Sie sich den Boost-Zeiger-Container von Thorsten Ottosen an
-Tipp Nr. 6 Suchen Sie einen Leckerkennungs-Header für die Plattform Ihrer Wahl, z. B. den "vld" -Header von Visual Leak Detection
quelle
Wenn Sie können, verwenden Sie boost shared_ptr und Standard C ++ auto_ptr. Diese vermitteln Besitzersemantik.
Wenn Sie ein auto_ptr zurückgeben, teilen Sie dem Anrufer mit, dass Sie ihm den Besitz des Speichers übertragen.
Wenn Sie einen shared_ptr zurückgeben, teilen Sie dem Anrufer mit, dass Sie einen Verweis darauf haben und er Teil des Eigentums ist, aber es liegt nicht nur in seiner Verantwortung.
Diese Semantik gilt auch für Parameter. Wenn der Anrufer Ihnen ein auto_ptr übergibt, gibt er Ihnen das Eigentum.
quelle
Andere haben Möglichkeiten erwähnt, Speicherlecks zu vermeiden (wie intelligente Zeiger). Ein Tool zur Profilerstellung und Speicheranalyse ist jedoch häufig die einzige Möglichkeit, Speicherprobleme aufzuspüren, sobald Sie sie haben.
Valgrind Memcheck ist ein ausgezeichneter kostenloser.
quelle
Fügen Sie nur für MSVC am Anfang jeder CPP-Datei Folgendes hinzu:
Wenn Sie dann mit VS2003 oder höher debuggen, werden Sie beim Beenden Ihres Programms über eventuelle Lecks informiert (es verfolgt Neu / Löschen). Es ist einfach, aber es hat mir in der Vergangenheit geholfen.
quelle
valgrind (nur für * nix-Plattformen verfügbar) ist eine sehr schöne Speicherprüfung
quelle
Wenn Sie Ihren Speicher manuell verwalten möchten, haben Sie zwei Fälle:
Wenn Sie gegen eine dieser Regeln verstoßen müssen, dokumentieren Sie diese bitte.
Es geht um Zeigerbesitz.
quelle
Eigentlich ist das auch der Mechanismus, der von "Smart Pointern" verwendet wird und von einigen anderen Autoren als RAII bezeichnet wird ;-).
Auf diese Weise können Sie bei Bedarf auf einfache Weise eine Debug-Ausgabe erstellen (welche Adressen zugewiesen und freigegeben werden, ...).
quelle
Sie können die Speicherzuweisungsfunktionen abfangen und feststellen, ob beim Beenden des Programms einige Speicherzonen nicht freigegeben wurden (obwohl dies nicht für alle Anwendungen geeignet ist ).
Dies kann auch zur Kompilierungszeit erfolgen, indem die Operatoren new und delete sowie andere Speicherzuweisungsfunktionen ersetzt werden.
Beispiel: Überprüfen Sie diese Site [Debuggen der Speicherzuordnung in C ++]. Hinweis: Es gibt einen Trick für den Löschoperator, der auch so aussieht:
Sie können in einigen Variablen den Namen der Datei speichern und wann der überladene Löschoperator weiß, von welchem Ort aus er aufgerufen wurde. Auf diese Weise können Sie die Verfolgung aller Löschvorgänge und Mallocs aus Ihrem Programm abrufen. Am Ende der Speicherüberprüfungssequenz sollten Sie in der Lage sein zu melden, welcher zugewiesene Speicherblock nicht "gelöscht" wurde, und ihn anhand des Dateinamens und der Zeilennummer identifizieren, was ich denke, was Sie wollen.
Sie können auch etwas wie BoundsChecker unter Visual Studio ausprobieren, das ziemlich interessant und einfach zu bedienen ist.
quelle
Wir verpacken alle unsere Zuordnungsfunktionen mit einer Ebene, die vorne eine kurze Zeichenfolge und am Ende ein Sentinel-Flag anfügt. So würden Sie beispielsweise "myalloc (pszSomeString, iSize, iAlignment)" oder "new (" description ", iSize) MyObject ()" aufrufen, das intern die angegebene Größe und genügend Speicherplatz für Ihren Header und Sentinel zuweist. Natürlich Vergessen Sie nicht, dies für Nicht-Debug-Builds zu kommentieren! Dies erfordert etwas mehr Speicher, aber die Vorteile überwiegen bei weitem die Kosten.
Dies hat drei Vorteile: Erstens können Sie einfach und schnell nachverfolgen, welcher Code ausläuft, indem Sie schnell nach Code suchen, der in bestimmten "Zonen" zugewiesen, aber nicht bereinigt wurde, wenn diese Zonen freigegeben werden sollten. Es kann auch nützlich sein, zu erkennen, wann eine Grenze überschrieben wurde, indem überprüft wird, ob alle Sentinels intakt sind. Dies hat uns viele Male erspart, als wir versucht haben, diese gut versteckten Abstürze oder Array-Fehltritte zu finden. Der dritte Vorteil besteht darin, die Verwendung des Speichers zu verfolgen, um festzustellen, wer die großen Player sind. Eine Zusammenstellung bestimmter Beschreibungen in einem MemDump zeigt Ihnen beispielsweise, wann „Sound“ viel mehr Platz beansprucht, als Sie erwartet haben.
quelle
C ++ wurde unter Berücksichtigung von RAII entwickelt. Es gibt wirklich keinen besseren Weg, um Speicher in C ++ zu verwalten, denke ich. Achten Sie jedoch darauf, dem lokalen Bereich keine sehr großen Blöcke (wie Pufferobjekte) zuzuweisen. Dies kann zu Stapelüberläufen führen. Wenn bei der Überprüfung der Grenzen während der Verwendung dieses Blocks ein Fehler auftritt, können Sie andere Variablen überschreiben oder Adressen zurückgeben, was zu Sicherheitslücken aller Art führt.
quelle
Eines der wenigen Beispiele für das Zuweisen und Zerstören an verschiedenen Orten ist die Thread-Erstellung (der Parameter, den Sie übergeben). Aber auch in diesem Fall ist es einfach. Hier ist die Funktion / Methode, die einen Thread erstellt:
Hier stattdessen die Thread-Funktion
Ziemlich einfach, nicht wahr? Falls die Thread-Erstellung fehlschlägt, wird die Ressource vom auto_ptr freigegeben (gelöscht), andernfalls wird der Besitz an den Thread übergeben. Was ist, wenn der Thread so schnell ist, dass er nach der Erstellung die Ressource vor dem freigibt?
wird in der Hauptfunktion / Methode aufgerufen? Nichts! Weil wir dem auto_ptr sagen, dass es die Freigabe ignorieren soll. Ist die C ++ - Speicherverwaltung einfach? Prost,
Ema!
quelle
Verwalten Sie den Speicher genauso wie andere Ressourcen (Handles, Dateien, Datenbankverbindungen, Sockets ...). GC würde Ihnen auch nicht dabei helfen.
quelle
Genau eine Rückkehr von jeder Funktion. Auf diese Weise können Sie dort eine Freigabe vornehmen und diese nie verpassen.
Ansonsten ist es zu leicht, einen Fehler zu machen:
quelle