Wie finde ich Speicherlecks in einem C++-Code/Projekt?
Lesezeit: 15 Minuten
Vikram Ranabhatt
Ich bin ein C++-Programmierer auf der Windows-Plattform. Ich verwende Visual Studio 2008.
Normalerweise lande ich im Code mit Speicherlecks.
Normalerweise finde ich das Speicherleck, indem ich den Code inspiziere, aber es ist umständlich und nicht immer ein guter Ansatz.
Da ich mir kein kostenpflichtiges Tool zur Erkennung von Speicherlecks leisten kann, wollte ich, dass Sie die bestmöglichen Methoden zur Vermeidung von Speicherlecks vorschlagen.
Ich möchte wissen, wie der Programmierer Speicherlecks finden kann.
Gibt es einen Standard oder ein Verfahren, das befolgt werden sollte, um sicherzustellen, dass es im Programm keinen Speicherverlust gibt?
“Normalerweise lande ich im Code mit Memory Leak.” Wenn Sie automatische Variablen, Container und intelligente Zeiger verwenden (und bewährte Methoden für die Verwendung von intelligenten Zeigern befolgen), sollten Speicherlecks äußerst selten sein. Denken Sie daran, dass Sie in fast allen Fällen die automatische Ressourcenverwaltung verwenden sollten.
– James McNellis
7. Juni 2011 um 6:12 Uhr
Doppelte Probleme, die von mehreren Fragen behandelt werden, wie stackoverflow.com/questions/1502799/… und stackoverflow.com/questions/2820223/…
– HostileFork sagt, vertraue SE nicht
7. Juni 2011 um 6:13 Uhr
@Hostile Fork: „Wie kann man das vermeiden meistens mit Speicherlecks im Code landen” wird von diesen Antworten nicht abgedeckt.
– Doc Braun
7. Juni 2011 um 8:29 Uhr
@Doc Brown: Ich hatte keine Lust, das auch nachzuschlagen, aber es ist alles an anderer Stelle behandelt, wie zum Beispiel stackoverflow.com/questions/45627/…
– HostileFork sagt, vertraue SE nicht
7. Juni 2011 um 8:33 Uhr
DIY Leak Detector: Sie könnten einen verdächtigen Code in eine Endlosschleife stecken und dann einen Task-Manager öffnen, normalerweise füllt sogar ein kleines Leck den Speicher in Sekunden oder Minuten (das hängt von Ihrer Codekomplexität und Ihrer CPU ab). Wenn dies nicht geschieht, ist dieser Codeabschnitt wahrscheinlich nicht undicht.
– Hallo Welt
15. Januar 2015 um 20:41 Uhr
John Smith
Anweisungen
Dinge, die du brauchen wirst
Kenntnisse in C++
C++-Compiler
Debugger und andere investigative Softwaretools
1
Verstehen Sie die Bedienergrundlagen. Der C++-Operator new weist Heap-Speicher zu. Das delete Operator gibt Heap-Speicher frei. Für jeden newsollten Sie a verwenden delete damit Sie denselben Speicher freigeben, den Sie zugewiesen haben:
char* str = new char [30]; // Allocate 30 bytes to house a string.
delete [] str; // Clear those 30 bytes and make str point nowhere.
2
Ordnen Sie Speicher nur dann neu zu, wenn Sie ihn gelöscht haben. Im folgenden Code str erhält mit der zweiten Zuweisung eine neue Adresse. Die erste Adresse geht unwiederbringlich verloren, ebenso die 30 Bytes, auf die sie zeigte. Jetzt ist es unmöglich, sie zu befreien, und Sie haben ein Speicherleck:
char* str = new char [30]; // Give str a memory address.
// delete [] str; // Remove the first comment marking in this line to correct.
str = new char [60]; /* Give str another memory address with
the first one gone forever.*/
delete [] str; // This deletes the 60 bytes, not just the first 30.
3
Beobachten Sie diese Zeigerzuweisungen. Jede dynamische Variable (zugewiesener Speicher auf dem Heap) muss einem Zeiger zugeordnet werden. Wenn eine dynamische Variable von ihren Zeigern getrennt wird, kann sie nicht mehr gelöscht werden. Auch dies führt zu einem Speicherleck:
char* str1 = new char [30];
char* str2 = new char [40];
strcpy(str1, "Memory leak");
str2 = str1; // Bad! Now the 40 bytes are impossible to free.
delete [] str2; // This deletes the 30 bytes.
delete [] str1; // Possible access violation. What a disaster!
4
Seien Sie vorsichtig mit lokalen Zeigern. Ein Zeiger, den Sie in einer Funktion deklarieren, wird auf dem Stapel zugewiesen, aber die dynamische Variable, auf die er zeigt, wird auf dem Heap zugewiesen. Wenn Sie es nicht löschen, bleibt es bestehen, nachdem das Programm die Funktion beendet hat:
void Leak(int x){
char* p = new char [x];
// delete [] p; // Remove the first comment marking to correct.
}
5
Achten Sie auf die eckigen Klammern hinter „delete“. Benutzen delete allein, um ein einzelnes Objekt zu befreien. Benutzen delete [] mit eckigen Klammern, um ein Heap-Array freizugeben. Mach sowas nicht:
char* one = new char;
delete [] one; // Wrong
char* many = new char [30];
delete many; // Wrong!
6
Wenn das Leck noch erlaubt ist – ich suche es normalerweise mit Deleaker (überprüfen Sie es hier: http://deleaker.com).
Entschuldigung für den Frage-Kommentar, aber was ist mit Funktionsparametern ohne Zeiger? someFunction("some parameter") muss ich löschen "some parameter" in dem someFunctionnach dem Funktionsaufruf, oder werden diese automatisch gelöscht?
– 19greg96
26. Mai 2013 um 17:31 Uhr
vielen Dank für den Link zu Deleaker, dies ist ein wirklich praktisches Tool mit einer sauberen Integration in Visual Studio. Ich könnte viel Zeit sparen, wenn ich es benutze. wies mich auf die Zeilen hin, in denen ich Speicher zugewiesen und nicht freigegeben habe. Toll. Und es ist billig im Vergleich zu anderen Speicherlecksuchern, die ich gefunden habe.
– das.mich
14. Januar 2015 um 16:22 Uhr
@ john smith plz erklären, was der richtige Weg ist, Fälle ähnlich wie Fall 3 zu behandeln; str2 = str1; // Schlecht! Jetzt sind die 40 Bytes nicht mehr freizugeben. wie lösche ich dann str 1??
– Nihar
15. Juni 2015 um 8:44 Uhr
Was ist, wenn wir Werttypen wie char*, int, float, … und Strukturen wie Vector, CString verwenden und überhaupt keinen “neuen” Operator verwenden, wird dies kein Speicherleck verursachen, oder?
– 123iamking
16. Mai 2016 um 12:01 Uhr
Ich bin nur hier, um zu sagen, dass ich c++ seit fast 14 Jahren nicht mehr angefasst habe … aber ich bin stolz darauf, sagen zu können, dass ich dank eines C++-Buches, das ich immer noch besitze und lese, verstanden und mich daran erinnert habe, wie man das alles macht Ich langweile mich mit c#. Dieses Buch ist „Effective C++“ von Scott Mitchell. Gott, ich habe dieses Buch geliebt. Danke Scott!
– JonH
25. Oktober 2016 um 1:57 Uhr
Sie können einige Techniken in Ihrem Code verwenden, um Speicherverluste zu erkennen. Die gebräuchlichste und einfachste Methode zur Erkennung besteht darin, ein Makro zu definieren, z. B. DEBUG_NEW, und es zusammen mit vordefinierten Makros wie z __FILE__ und __LINE__ um das Speicherleck in Ihrem Code zu lokalisieren. Diese vordefinierten Makros teilen Ihnen die Datei- und Zeilennummer von Speicherlecks mit.
DEBUG_NEW ist nur ein MAKRO, das normalerweise definiert wird als:
#define DEBUG_NEW new(__FILE__, __LINE__)
#define new DEBUG_NEW
Damit, wo immer Sie es verwenden newkann es auch die Datei- und Zeilennummer verfolgen, die verwendet werden könnte, um Speicherlecks in Ihrem Programm zu lokalisieren.
Und __FILE__, __LINE__ sind vordefinierte Makros die den Dateinamen und die Zeilennummer auswerten bzw. wo Sie sie verwenden!
Lesen Sie den folgenden Artikel, der die Technik der Verwendung von DEBUG_NEW mit anderen interessanten Makros sehr schön erklärt:
Debug_new bezieht sich auf eine Technik in C++, um den Operator new und den Operator delete zu überladen und/oder neu zu definieren, um die Speicherzuordnungs- und -aufhebungsaufrufe abzufangen und somit ein Programm auf Speichernutzung zu debuggen. Es beinhaltet oft die Definition eines Makros namens DEBUG_NEW und macht aus new so etwas wie new(_DATEI_, _LINIE_), um die Datei-/Zeileninformationen bei der Zuordnung aufzuzeichnen. Microsoft Visual C++ verwendet diese Technik in seinen Microsoft Foundation Classes. Es gibt einige Möglichkeiten, diese Methode zu erweitern, um die Makroneudefinition zu vermeiden, während die Datei-/Zeileninformationen auf einigen Plattformen weiterhin angezeigt werden können. Es gibt viele inhärente Beschränkungen für dieses Verfahren. Es gilt nur für C++ und kann keine Speicherlecks durch C-Funktionen wie malloc abfangen. Es kann jedoch sehr einfach zu verwenden und auch sehr schnell sein, verglichen mit einigen umfassenderen Speicher-Debugger-Lösungen.
diese #define wird mit überladen durcheinander bringen operator new und Compilerfehler generieren. Selbst wenn es Ihnen gelingt, das zu überwinden, werden die überladenen Funktionen immer noch nicht angesprochen. Obwohl die Technik gut ist, sind manchmal viele Codeänderungen erforderlich.
– iammilind
7. Juni 2011 um 6:21 Uhr
@iammilind: Natürlich ist diese Technik keine allheilende Lösung für alle Probleme und sicherlich nicht in allen Situationen anwendbar.
– Nawaz
7. Juni 2011 um 6:26 Uhr
@Chris_vr: auto_ptr funktioniert nicht mit Standardbehältern wie z std::vector, std::list usw. Siehe dies: stackoverflow.com/questions/111478/…
– Nawaz
7. Juni 2011 um 6:32 Uhr
Ok cool. DATEI und Linie werden beschrieben. Was ist operator new und was sind diese Versionen davon, die Sie verwenden?
– Benutzer645280
6. Mai 2014 um 14:51 Uhr
Gal Nachmana
Das Ausführen von “Valgrind” kann:
1) Helfen Sie, Speicherlecks zu identifizieren – Zeigen Sie, wie viele Speicherlecks Sie haben, und weisen Sie auf die Zeilen im Code hin, in denen der Speicherleck zugewiesen wurde.
2) Weisen Sie auf falsche Versuche hin, Speicher freizugeben (z. B. unsachgemäßer Aufruf von delete)
Wobei “myprog” Ihr kompiliertes Programm ist und arg1, arg2 die Argumente Ihres Programms.
4) Das Ergebnis ist eine Liste mit Anrufen malloc/new das hatte keine nachträglichen anrufe zum kostenlosen löschen.
Zum Beispiel:
==4230== at 0x1B977DD0: malloc (vg_replace_malloc.c:136)
==4230== by 0x804990F: main (example.c:6)
Sagt Ihnen, in welcher Zeile die malloc (der nicht befreit wurde) wurde gerufen.
Stellen Sie, wie von anderen betont, dies für alle sicher new/malloc Rufen Sie an, Sie haben eine nachfolgende delete/free Forderung.
Doktor Braun
Es gibt einige bekannte Programmiertechniken, die Ihnen helfen, das Risiko von Speicherlecks aus erster Hand zu minimieren:
Wenn Sie Ihre eigene dynamische Speicherzuweisung vornehmen müssen, schreiben Sie new und delete immer paarweise, und stellen Sie sicher, dass der Zuordnungs-/Deallocation-Code paarweise aufgerufen wird
Vermeiden Sie die dynamische Speicherzuweisung, wenn Sie können. Verwenden Sie zum Beispiel vector<T> t wo immer möglich statt T* t = new T[size]
Mein persönlicher Favorit: Stellen Sie sicher, dass Sie das Konzept des Eigentümers eines Zeigers verstanden haben, und stellen Sie sicher, dass Sie überall dort, wo Sie Zeiger verwenden, wissen, welche Code-Entität der Eigentümer ist
Erfahren Sie, welche Konstruktoren/Zuweisungsoperatoren automatisch vom C++-Compiler erstellt werden und was das bedeutet, wenn Sie eine Klasse haben, die einen Zeiger besitzt (oder was das bedeutet, wenn Sie eine Klasse haben, die einen Zeiger auf ein Objekt enthält, das sie besitzt nicht besitzen).
Ciro Santilli Путлер Капут 六四事
Eine Übersicht über automatische Speicherleckprüfer
In dieser Antwort vergleiche ich mehrere verschiedene Speicherleckprüfer in einem einfachen, leicht verständlichen Beispiel für Speicherlecks.
Die Ausgabe des Programmlaufs enthält die Memory-Leak-Analyse:
WARNING: Perftools heap leak checker is active -- Performance may suffer
Starting tracking the heap
Dumping heap profile to ble.0001.heap (Exiting, 4 kB in use)
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 272 bytes in 2 objects
The 2 largest leaks:
Using local file ./main.out.
Leak of 256 bytes in 1 objects allocated from:
@ 555bf6e5815d my_malloc
@ 555bf6e5817a leaky
@ 555bf6e581d3 main
@ 7f71e88c9b6b __libc_start_main
@ 555bf6e5808a _start
Leak of 16 bytes in 1 objects allocated from:
@ 555bf6e5815d my_malloc
@ 555bf6e5817a leaky
@ 555bf6e581b5 main
@ 7f71e88c9b6b __libc_start_main
@ 555bf6e5808a _start
If the preceding stack traces are not enough to find the leaks, try running THIS shell command:
pprof ./main.out "/tmp/main.out.24744._main_-end.heap" --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --gv
If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks more re
Exiting with error code (instead of crashing) because of whole-program memory leaks
und die Ausgabe von google-pprof enthält die Analyse der Heap-Nutzung:
Using local file main.out.
Using local file ble.0001.heap.
Total: 0.0 MB
0.0 100.0% 100.0% 0.0 100.0% my_malloc
0.0 0.0% 100.0% 0.0 100.0% __libc_start_main
0.0 0.0% 100.0% 0.0 100.0% _start
0.0 0.0% 100.0% 0.0 100.0% leaky
0.0 0.0% 100.0% 0.0 100.0% main
Die Ausgabe weist uns auf zwei der drei Leaks hin:
Leak of 256 bytes in 1 objects allocated from:
@ 555bf6e5815d my_malloc
@ 555bf6e5817a leaky
@ 555bf6e581d3 main
@ 7f71e88c9b6b __libc_start_main
@ 555bf6e5808a _start
Leak of 16 bytes in 1 objects allocated from:
@ 555bf6e5815d my_malloc
@ 555bf6e5817a leaky
@ 555bf6e581b5 main
@ 7f71e88c9b6b __libc_start_main
@ 555bf6e5808a _start
Ich bin mir nicht sicher, warum der dritte nicht aufgetaucht ist
Auf jeden Fall passiert es oft, wenn etwas leckt, und wenn ich es bei einem echten Projekt verwendet habe, wurde ich sehr einfach auf die Leckfunktion hingewiesen.
Wie in der Ausgabe selbst erwähnt, führt dies zu einer erheblichen Verlangsamung der Ausführung.
=================================================================
==27223==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 4096 byte(s) in 1 object(s) allocated from:
#0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
#1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
#2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
#3 0x55bf86c5f210 in main /home/ciro/test/main.c:20
#4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
Direct leak of 256 byte(s) in 1 object(s) allocated from:
#0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
#1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
#2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
#3 0x55bf86c5f1f2 in main /home/ciro/test/main.c:18
#4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
Direct leak of 16 byte(s) in 1 object(s) allocated from:
#0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
#1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
#2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
#3 0x55bf86c5f1d4 in main /home/ciro/test/main.c:16
#4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
SUMMARY: AddressSanitizer: 4368 byte(s) leaked in 3 allocation(s).
die alle Lecks eindeutig identifiziert. Hübsch!
ASan kann auch andere coole Prüfungen durchführen, wie z. B. Out-of-Bounds-Schreibvorgänge: Stack-Smashing erkannt
==32178== Memcheck, a memory error detector
==32178== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==32178== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==32178== Command: ./main.out
==32178==
==32178==
==32178== HEAP SUMMARY:
==32178== in use at exit: 4,368 bytes in 3 blocks
==32178== total heap usage: 6 allocs, 3 frees, 8,736 bytes allocated
==32178==
==32178== 16 bytes in 1 blocks are definitely lost in loss record 1 of 3
==32178== at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178== by 0x10915C: my_malloc (main.c:4)
==32178== by 0x109179: leaky (main.c:8)
==32178== by 0x1091B4: main (main.c:16)
==32178==
==32178== 256 bytes in 1 blocks are definitely lost in loss record 2 of 3
==32178== at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178== by 0x10915C: my_malloc (main.c:4)
==32178== by 0x109179: leaky (main.c:8)
==32178== by 0x1091D2: main (main.c:18)
==32178==
==32178== 4,096 bytes in 1 blocks are definitely lost in loss record 3 of 3
==32178== at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178== by 0x10915C: my_malloc (main.c:4)
==32178== by 0x109179: leaky (main.c:8)
==32178== by 0x1091F0: main (main.c:20)
==32178==
==32178== LEAK SUMMARY:
==32178== definitely lost: 4,368 bytes in 3 blocks
==32178== indirectly lost: 0 bytes in 0 blocks
==32178== possibly lost: 0 bytes in 0 blocks
==32178== still reachable: 0 bytes in 0 blocks
==32178== suppressed: 0 bytes in 0 blocks
==32178==
==32178== For counts of detected and suppressed errors, rerun with: -v
==32178== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
Also wurden wieder einmal alle Lecks entdeckt.
Siehe auch: Wie verwende ich valgrind, um Speicherlecks zu finden?
Verwenden Sie die gflags Dienstprogramm zum Aktivieren von Stack-Traces im Benutzermodus.
Benutzen UMDH um mehrere Schnappschüsse Ihres Programmspeichers zu machen. Machen Sie einen Schnappschuss, bevor Speicher zugewiesen wird, und machen Sie einen zweiten Schnappschuss nach einem Punkt, an dem Sie glauben, dass Ihr Programm Speicher verloren hat. Vielleicht möchten Sie Ihrem Programm Pausen oder Eingabeaufforderungen hinzufügen, um Ihnen die Möglichkeit zu geben, zu laufen UMDH und die Schnappschüsse machen.
Lauf UMDH wieder, diesmal in seinem Modus, der einen Unterschied zwischen den beiden Schnappschüssen macht. Anschließend wird ein Bericht erstellt, der die Aufruflisten vermuteter Speicherlecks enthält.
Stellen Sie Ihre vorherige wieder her gflags Einstellungen, wenn Sie fertig sind.
UMDH gibt Ihnen mehr Informationen als der CRT-Debug-Heap, da er die Speicherzuweisungen über Ihren gesamten Prozess überwacht; Es kann Ihnen sogar sagen, ob Komponenten von Drittanbietern undicht sind.
Ich bevorzuge Deleaker und Valgrind anstelle des Standardprofilers
– z0r1fan
24. Dezember 2018 um 18:08 Uhr
10132700cookie-checkWie finde ich Speicherlecks in einem C++-Code/Projekt?yes
“Normalerweise lande ich im Code mit Memory Leak.” Wenn Sie automatische Variablen, Container und intelligente Zeiger verwenden (und bewährte Methoden für die Verwendung von intelligenten Zeigern befolgen), sollten Speicherlecks äußerst selten sein. Denken Sie daran, dass Sie in fast allen Fällen die automatische Ressourcenverwaltung verwenden sollten.
– James McNellis
7. Juni 2011 um 6:12 Uhr
Doppelte Probleme, die von mehreren Fragen behandelt werden, wie stackoverflow.com/questions/1502799/… und stackoverflow.com/questions/2820223/…
– HostileFork sagt, vertraue SE nicht
7. Juni 2011 um 6:13 Uhr
@Hostile Fork: „Wie kann man das vermeiden meistens mit Speicherlecks im Code landen” wird von diesen Antworten nicht abgedeckt.
– Doc Braun
7. Juni 2011 um 8:29 Uhr
@Doc Brown: Ich hatte keine Lust, das auch nachzuschlagen, aber es ist alles an anderer Stelle behandelt, wie zum Beispiel stackoverflow.com/questions/45627/…
– HostileFork sagt, vertraue SE nicht
7. Juni 2011 um 8:33 Uhr
DIY Leak Detector: Sie könnten einen verdächtigen Code in eine Endlosschleife stecken und dann einen Task-Manager öffnen, normalerweise füllt sogar ein kleines Leck den Speicher in Sekunden oder Minuten (das hängt von Ihrer Codekomplexität und Ihrer CPU ab). Wenn dies nicht geschieht, ist dieser Codeabschnitt wahrscheinlich nicht undicht.
– Hallo Welt
15. Januar 2015 um 20:41 Uhr