Forum: PC-Programmierung Finding Memory Leaks Using the CRT Library


von A. R. (redegle)


Lesenswert?

Hallo,

ich habe in Visual Studio 2010 ein Programm in C++ als 
Win32-Konsolenanwendung geschrieben. Da das Programm mitlerweile sehr 
Umfangreich geworden ist (mehr als 1000Zeilen in mehreren Dateien) 
möchte ich nicht den gesamten Code hochladen. Ich bitte hier um 
Verständnis, wenn notwendig kann ich später weitere Teile des Codes 
hochladen. Das Problem ist, dass ich ein "kleines" Speicherloch habe. 
Nach dem Start verbraucht das Programm 3.344K Speicher. Dieser nimmt 
dann mit der Zeit zu.

3:00Uhr      3.344K
4:30Uhr      7.400K
12:38Uhr     33.424K
15:06Uhr     41.312K
.
.
.

Leider konnte ich das memory leaking bis jetzt noch nicht ausfindig 
machen und such eine Möglichkeit, einen Hinweis zu bekommen, welche 
Variable das Problem der Ursache ist.
Hierzu habe ich online folgende Links gefunden.

http://msdn.microsoft.com/en-us/library/x98tx3cf%28v=VS.100%29.aspx
http://www.david-amador.com/2010/10/tracking-memory-leaks-in-visual-studio/

Dort wird beschrieben, wie ich in Visual Studio 2010 eine Ausgabe 
bekomme, die mir einen Hinweis zu dem Fehler gibt. Leider funktioniert 
dies bei meinem Programm nicht. Ich hoffe, dass hier jemand ist, der 
diese Funktionalität schon erfolgreich angewendet hat und bereit ist mir 
zu helfen.

Im Header "stdafx.h" habe ich folgenden Code ergänzt.
1
#define _CRTDBG_MAP_ALLOC
2
#include <stdlib.h>
3
#include <crtdbg.h>

Direkt am Anfang von der main() habe ich folgenden Code ergänzt.
1
#ifdef _DEBUG
2
int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
3
flag |= _CRTDBG_LEAK_CHECK_DF; // Turn on leak-checking bit
4
_CrtSetDbgFlag(flag);
5
#endif

Ich hätte jetzt gedacht, dass nach dem ordnungsgemäßem Beenden des 
Programms eine Ausgabe stattfindet. Leider konnte ich nicht 
herrausfinden, wo diese Ausgabe stehen soll (wahrscheinlich im 
Ausgabfenster von Visual Studio) und ich konnte auch keine Ausgabe 
sehen.

von Klaus W. (mfgkw)


Lesenswert?

Steht doch bei deine Link:
> After you have enabled the debug heap functions by using
> these statements, you can place a call to _CrtDumpMemoryLeaks
> before an application exit point to display a memory-leak
> report when your application exits:

Machst du das auch?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

A. R. schrieb:
> Leider konnte ich nicht herrausfinden, wo diese Ausgabe stehen
> soll (wahrscheinlich im Ausgabfenster von Visual Studio)

Genau da landet sie. Da steht beim Programmstart, welche DLLs geladen 
werden, da werden Ausgaben via TRACE bzw. OutputDebugString angezeigt, 
und dort wird beim Beenden des Programmes auch eine Übersicht über 
Speicherlecks ausgegeben.

Wenn Du in allen Klassenimplementierungen das hier drinstehen hast:
1
#ifdef _DEBUG
2
#define new DEBUG_NEW
3
#undef THIS_FILE
4
static char THIS_FILE[] = __FILE__;
5
#endif

dann wird in der Übersicht auch gleich die Stelle angezeigt, an der der 
betreffende Speicherblock angefordert wurde.

von A. R. (redegle)


Lesenswert?

Klaus Wachtler schrieb:
> Steht doch bei deine Link:
>> After you have enabled the debug heap functions by using
>> these statements, you can place a call to _CrtDumpMemoryLeaks
>> before an application exit point to display a memory-leak
>> report when your application exits:
>
> Machst du das auch?

NEIN

Ich mache dieses hier:

>If your application has multiple exits, you do not need to manually place a >call 
to _CrtDumpMemoryLeaks at every exit point. A call to _CrtSetDbgFlag >at the 
beginning of your application will cause an automatic call to >_CrtDumpMemoryLeaks 
at each exit point. You must set the two bit fields >shown here:

Wobei ich sagen muss, dass ich momentan noch ein paar Threads beim 
beenden des Programms "hart" beende.

von DanDanger (Gast)


Lesenswert?

Ich benutze zum auffinden von Speicherlecks immer den "Visual Leak 
Detector" (VLD).
OpenSource, kostenlos und einfach zu benutzen ;-)
=> "http://vld.codeplex.com/";

von A. R. (redegle)


Lesenswert?

@DanDanger,

danke für die Information.


Das Problem war, dass ich ein paar Threads nicht richtig beende sondern 
einfach das Programm schließe. Habe das Programm nun so abgeändert, dass 
jeder Thread zuerst beendet wird, bevor das Programm geschlossen wird. 
Jetzt klappt auch die Ausgabe von allen Speicherleaks.

Danke für die Hilfe.

von A. R. (redegle)


Lesenswert?

Rufus Τ. Firefly schrieb:
> A. R. schrieb:
>> Leider konnte ich nicht herrausfinden, wo diese Ausgabe stehen
>> soll (wahrscheinlich im Ausgabfenster von Visual Studio)
>
> Genau da landet sie. Da steht beim Programmstart, welche DLLs geladen
> werden, da werden Ausgaben via TRACE bzw. OutputDebugString angezeigt,
> und dort wird beim Beenden des Programmes auch eine Übersicht über
> Speicherlecks ausgegeben.
>
> Wenn Du in allen Klassenimplementierungen das hier drinstehen hast:
>
>
1
> #ifdef _DEBUG
2
> #define new DEBUG_NEW
3
> #undef THIS_FILE
4
> static char THIS_FILE[] = __FILE__;
5
> #endif
6
>
>
> dann wird in der Übersicht auch gleich die Stelle angezeigt, an der der
> betreffende Speicherblock angefordert wurde.

Könntest du das bitte nocheinmal etwas genauer erklären.
Wenn ich die Zeilen so in "stdafx.h" einfüge bekomme ich mehrere 
Fehlermeldungen beim compilieren.
U.A.:
DEBUG_NEW: nicht deklarierter Bezeichner.

Mittlerweile sehen meine Ergänzungen wie folgt aus.

"stdafx.h":
1
#define _CRTDBG_MAP_ALLOC
2
#define _CRTDBG_MAP_ALLOC_NEW
3
#include <stdlib.h>
4
#include <crtdbg.h>
5
6
#ifdef _DEBUG
7
   #ifndef DBG_NEW
8
      #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
9
      #define new DBG_NEW
10
   #endif
11
#endif  // _DEBUG

Zu Beginn von main:
1
#ifdef _DEBUG
2
//int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
3
//flag |= _CRTDBG_LEAK_CHECK_DF; // Turn on leak-checking bit
4
//_CrtSetDbgFlag(flag);
5
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
6
#endif

Es werden alle Speicherleaks angezeigt. Beim Allozieren mit new 
erscheint auch direkt ein Link zu der Stelle. Beim allozieren mit malloc 
bekomme ich auch eine Nummer mitgeteilt jedoch keinen Link. Lässt sich 
ein solcher Ergänzen? Ggf. ist das genau das was du meinst.

von Karl H. (kbuchegg)


Lesenswert?

A. R. schrieb:

>> Wenn Du in allen Klassenimplementierungen das hier drinstehen hast:
>>
>>
1
>> #ifdef _DEBUG
2
>> #define new DEBUG_NEW
3
>> #undef THIS_FILE
4
>> static char THIS_FILE[] = __FILE__;
5
>> #endif
6
>>
>>
>> dann wird in der Übersicht auch gleich die Stelle angezeigt, an der der
>> betreffende Speicherblock angefordert wurde.
>
> Könntest du das bitte nocheinmal etwas genauer erklären.
> Wenn ich die Zeilen so in "stdafx.h" einfüge bekomme ich mehrere
> Fehlermeldungen beim compilieren.
> U.A.:
> DEBUG_NEW: nicht deklarierter Bezeichner.
>
> Mittlerweile sehen meine Ergänzungen wie folgt aus.
>
> "stdafx.h":
>
1
> #define _CRTDBG_MAP_ALLOC
2
> #define _CRTDBG_MAP_ALLOC_NEW
3
> #include <stdlib.h>
4
> #include <crtdbg.h>
5
> 
6
> #ifdef _DEBUG
7
>    #ifndef DBG_NEW
8
>       #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
9
>       #define new DBG_NEW
10
>    #endif
11
> #endif  // _DEBUG
12
>

Das ist genau dasselbe. NUr heisst halt das Makro ein wenig anders.

> erscheint auch direkt ein Link zu der Stelle. Beim allozieren mit malloc
> bekomme ich auch eine Nummer mitgeteilt jedoch keinen Link.


Wieso hast du in einem C++ Programm mallocs drinnen?

(Schnell den Knoblauch rausholen, das Kruzifix bereitlegen und den 
Exorzisten anrufen. Mach dich auf was gefasst, malloc-Dämon!)

von A. R. (redegle)


Lesenswert?

Das Programm ist Historisch gewachsen.
Habe Code übernommen in dem mallocs enthalten sind.

Ich greife auch auf mehrere Bibliotheken zu.

von Mark B. (markbrandis)


Lesenswert?

A. R. schrieb:
> Das Programm ist Historisch gewachsen.
> Habe Code übernommen in dem mallocs enthalten sind.

Nur weil ein Programmierer vor Jahren der Meinug war, es wäre gut 
malloc() in C++ Code zu benutzen, heißt das ja nicht dass man diesen 
Blödsinn in alle Ewigkeit weiterführen muss.
Wenn es freilich Tausende Zeilen sind die man ändern müsste, um 
vernünftigen Code zu haben, wird man dafür oft genug die Zeit nicht 
haben, und dann bleibt der Code eben wie er ist (meistens schlecht).

Ach, wie gut kenn ich dieses Problem :-(

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

A. R. schrieb:
> Könntest du das bitte nocheinmal etwas genauer erklären.
> Wenn ich die Zeilen so in "stdafx.h" einfüge bekomme ich mehrere
> Fehlermeldungen beim compilieren.

Das gehört nicht in eine Headerdatei, sondern in jede C++-Datei. Und 
zwar nach das #include "stdafx.h", weil es nicht durch präcompilierte 
Headerdateien abgedeckt werden soll.

Darum schrieb ich ja auch Implementierung.

von A. R. (redegle)


Angehängte Dateien:

Lesenswert?

Es wäre sehr nett, wenn Ihr mir noch ein paar Fragen beantworten 
könntet.

@ Mark Brandis (markbrandis),

die erste Frage sollte sein, warum man in C++ kein malloc/free verwenden 
sollte. Ich habe hierzu jedoch einen informativen Link gefunden. Das hat 
sich also eigendlich schon erledigt. Aber vielleicht Interessiert das 
auch jemand anderen, deswgen der Link:
http://www.codeproject.com/KB/tips/newandmalloc.aspx

@Rufus Τ. Firefly (rufus) (Moderator),

kannst du bitte noch erklären, warum der Code unbedingt in jeder 
C++-Datei muss. Ich dachte, dass der Header "stdafx.h" durch das 
#include "stdafx.h" beim compilieren vor die C++-Datei gehangen wird. Es 
sollte also keinen Unterschied machen, ob der Code in der Headerdatei 
oder der C++-Datei steht.
Es wäre auch noch schön zu erfahren, was der Code macht oder zumindest 
wo ich nachlesen kann was "THIS_FILE" , "DEBUG_NEW" und "__FILE__" ist. 
Beim einbinden kommt die Fehlermeldun welche sich im Anhang befindet.



Ich konnte jetzt fast alle Speicherleaks beseitigen. Lustigerweise waren 
alle "kompilzieren" Operationen mit mehrdimensionalen Pointern richtig. 
Der Fehler trag beim allozieren eines einfach Strings auf (ich liebe 
Flüchtigkeitsfehler). Das ließ sich ganz einfach finden, da bei der 
Fehlerausgabe immer der Inhalt des allozierten Speichers drin steht.

Jetzt bleibt nur noch ein Problem und zwar bekomme ich folgenden Text 
ausgegeben.
1
Detected memory leaks!
2
Dumping objects ->
3
{406} normal block at 0x02ED4170, 24 bytes long.
4
 Data: <                > 00 00 00 00 00 00 00 00 1E 00 00 00 00 00 00 00 
5
{397} normal block at 0x010C5918, 24 bytes long.
6
 Data: <                > 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 
7
{390} normal block at 0x010C5720, 24 bytes long.
8
 Data: <                > 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 
9
{383} normal block at 0x010C4EC0, 24 bytes long.
10
 Data: <                > 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 
11
Object dump complete.

Jemand eine Idee, wie ich dem Fehler auf die Schliche kommen kann? Die 
Ziffern 383, 390, 397 und 406 sind scheinbar eine Nummerierung der 
Allozierungen. Leider schwanken diese von Durchlauf zu Durchlauf. Der 
Befehlt "_CrtSetBreakAlloc(406);" hilft mir also nicht weiter.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Wenn das in der präcompilierten Headerdatei steht, kann logischerweise 
das Macro __FILE__ nicht mehr aufgelöst werden und die Information, 
wo die betreffende Speicheranforderung stattgefunden hat, nicht 
ermittelt werden.

Solange Du aber statt new malloc verwendest, geht das sowieso nicht.

Würdest Du new verwenden, stünde in der Ausgabe der Typ des 
angeforderten Objekts und die Codezeile, an der es angelegt wurde 
(Dateiname/Zeilennummer).

von Rolf M. (rmagnus)


Lesenswert?

Gibt's eigentlich unter Windows immer noch nicht sowas wie valgrind? 
Damit muß man diese ganzen Klimmzüge und Verrenkungen gar nicht machen, 
und ob nun malloc oder new verwendet wird, ist auch egal.

von Karl H. (kbuchegg)


Lesenswert?

> die erste Frage sollte sein, warum man in C++ kein
> malloc/free verwenden sollte.

Ein Punkt, der mir auch in deinem Link fehlt, ist die Feststellung, dass 
man in C++ malloc/free bzw. new/delete oft auch gar nicht (direkt) 
braucht, wenn man in C nicht anders kann.

Warum?

Weil du in C++ die STL zur Verfügung hast. D.h. du musst lineare Listen, 
dynamisch wachsende Arrays, wachsende Strings, etc. alles nicht selbst 
implementieren, sondern du hast die Dinge bereits fix&fertig zur 
Verfügung und musst sie nur noch benutzen. Wo man sich in C mit
   char InputLine[80];
einen Ast abprogrammiert und doch nie sicher sein kann bzw. horrenden 
Aufwand treiben muss, um Benutzereingaben bulletproof zu machen (der zu 
Recht gefürchtete Buffer-Overflow lässt grüßen), hat man in C++ eine
   std::string InputLine;
und damit diese Sorge erst mal los.
Ganz abgesehen davon, dass dann plötzlich CopyConstruktor, Assignemnt 
Operator und Destruktur oft überflüssig werden und damit auch als 
Fehlerquelle weg fallen.

Nutze die Macht, Luke! Dazu ist sie da!

Wer C++ so programmiert, wie er C programmiert, hat es nicht besser 
verdient.

von Karl H. (kbuchegg)


Lesenswert?

A. R. schrieb:

> Jemand eine Idee, wie ich dem Fehler auf die Schliche kommen kann? Die
> Ziffern 383, 390, 397 und 406 sind scheinbar eine Nummerierung der
> Allozierungen. Leider schwanken diese von Durchlauf zu Durchlauf. Der
> Befehlt "_CrtSetBreakAlloc(406);" hilft mir also nicht weiter.

so groß sind aber die Differenzen in den Allokierungsnummern nicht.
Setz dir den Break auf 383 und geh dann von dort im SingleStep weiter 
(oder mach dir einen weiteren Breakpoint auf eine der Crt-Alloc 
Funktionen, bei denen das Programm vorbeikommt. Und dann siehst du dir 
an, wo die nächsten, sagen wir mal 10 Allokierungen herkommen. Einer von 
diesen wird es sein.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.