Seite 1 von 1

[C++] Erläuterung zu einem Makrobefehl

Verfasst: 28.05.2011, 21:35
von Brainsmith
Ich habe mich gestern mit memory leak tracking auseinander gesetzt. Das Ziel war es, den Allokationsort des memory leaks auszugeben.

Laut MSDN folgendes schreiben:

Code: Alles auswählen

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
anschließend ruft man am Ende des Programms

Code: Alles auswählen

_CrtDumpMemoryLeaks();
auf und man bekommt angeblich den Dateinamen mit Zeilennummer ausgegeben, wenn ein Pointer nicht gelöscht wird.
Das Makro _CRTDBG_MAP_ALLOC sorgt angeblich dafür, dass Dateiname und Zeilennummer mit ausgegeben werden. Das hat bei mir nicht geklappt, weil... ja.. warum eigentlich.. keine Ahnung. Laut Kommentaren im MSDN liegt es daran, dass der Precompiled header die Datei afx.h einbindet und die wiederum die crtdebug.h.
Das Makro _CRTDBG_MAP_ALLOC muss allerdings definiert sein, bevor crtdebug.h eingebunden wird, damit die Ausgabe funktioniert. Durch die Einbindung von afx.h in stdafx.h (dem precompiled Header) kann man sich das _CRTDBG_MAP_ALLOC auch fast sparen..

Zusätzlich zu dem bisher genannten Problem ergibt sich die Tatsache, dass man

Code: Alles auswählen

_CrtDumpMemoryLeaks();
eigentlich nach der Main Methode und nach Deallokierung sämtlicher Speichereinheiten aufrufen muss. Das bekommt man hin, indem man in der Main.cpp als erstes eine Struktur definiert:

Code: Alles auswählen

struct DumpMemoLeaks{
	~DumpMemoLeaks(){
		_CrtDumpMemoryLeaks()
	}
};
Anschließend erstellt man als erstes Objekt in der main.cpp eine Instanz dieser Struktur. Diese landet dann ganz unten auf dem Stack und wird dementsprechend als letztes gelöscht. Also wird erst nach Deallokierung aller mit "new" erstellten Sachen aufgeräumt, was ja das Ziel war.

Man kann auch einfach folgenden Code aufrufen, anstatt dier Struktur zu nutzen:

Code: Alles auswählen

int DebugFlagForCRTDEBUG= _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
Muss dann auch so früh wie möglich gemacht werden.

Es bleibt das Problem, dass ich keine Zeilennummern habe, vom Dateinamen mal ganz zu schweigen. Dann bin ich über folgenden Codeausschnitt gestolpert:

Code: Alles auswählen

#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
#endif

#endif
Es funktioniert perfekt und man kann Memoryleaks ziemilch schnell finden und beheben.
Daran sind in meinen Augen aber zwei Sachen komisch:

1. Ist folgendes nicht effizienter?

Code: Alles auswählen

#ifndef DEBUG_NEW
#define DEBUG_NEW 
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif
2. Ich verstehe nicht genau, was hier passiert. man tauscht also durch

Code: Alles auswählen

new(_NORMAL_BLOCK, __FILE__, __LINE__)
.
Wird also immer, wenn ich new eintippe ein anderer new-Operator aufgerufen, der Speicherblock, Dateinamen und Zeilennummer speichert?

Hier nochmal der ganze Code an einem Stück:
Der Anfang von stdafx.h:

Code: Alles auswählen

//define for proper output
#define _CRTDBG_MAP_ALLOC
//includes for memory leak tracking
#include <stdlib.h>
#include <crtdbg.h>

#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
#endif
//enabling memory leak detection after programm has exited.
int DebugFlagForCRTDEBUG=_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

Hier noch eine weitere Quellenangabe mit Pro und Contras zum Thema.

Re: [C++] Erläuterung zu einem Makrobefehl

Verfasst: 28.05.2011, 23:08
von Krishty
Brainsmith hat geschrieben:2. Ich verstehe nicht genau, was hier passiert. man tauscht also durch

Code: Alles auswählen

new(_NORMAL_BLOCK, __FILE__, __LINE__)
.
Wird also immer, wenn ich new eintippe ein anderer new-Operator aufgerufen, der Speicherblock, Dateinamen und Zeilennummer speichert?
Japp. Zusätzliche Parameter werden bei überladenem new in der Mitte übergeben, hast du also deklariert:
void * operator new (size_t size, foo theFooParameter);
dann rufst du den per
Type * newInstance = new (myFoo) Type;
auf. Ergo hat die CRT einen operator new überladen, der als zweiten bis vierten Parameter int (dazu sind _NORMAL_BLOCK und _CLIENT_BLOCK #definet), char const * und int erwartet. Und der wird dann jedes Mal aufgerufen, wenn du new schreibst.

Re: [C++] Erläuterung zu einem Makrobefehl

Verfasst: 31.05.2011, 08:50
von Brainsmith
Danke für die Antwort. Verwirrt mich aber doch schon irgendwie. Schließlich steht dann nach dem Makro hinterher:

Code: Alles auswählen

new [Typ] ([Usereingaben]) ([Speicherblock], [Dateiname],[Zeilennummer])
Aber eine Funktion ruft doch immer nur den nächsten Klammerblock auf, oder?


Ich habe den memory leak detector letztens mal bei einem Kollegen implementiert und allein die Memoryleak-Ausgabe in eine Textdatei hat 3,5 MB verbraucht. Das hat man davon, wenn man ein Projekt von VB.NET nach VC++ konvertiert.. =D

Re: [C++] Erläuterung zu einem Makrobefehl

Verfasst: 31.05.2011, 08:56
von Krishty
Brainsmith hat geschrieben:Danke für die Antwort. Verwirrt mich aber doch schon irgendwie. Schließlich steht dann nach dem Makro hinterher:

Code: Alles auswählen

new [Typ] ([Usereingaben]) ([Speicherblock], [Dateiname],[Zeilennummer])
Nö.
new int(1);
wird zu
new (_NORMAL_BLOCK, "foo.cpp", 42) int(1);
und ruft erst den Operator
void * new(size_t size, int blockType, char const * file, int line);
und dann den Kopierkonstruktor von int auf.
Brainsmith hat geschrieben:Aber eine Funktion ruft doch immer nur den nächsten Klammerblock auf, oder?
new ist ja auch keine Funktion, sondern ein Operator. Wenn du x << y; schreibst, sind da überhaupt keine Klammern und es wird trotzdem was mit Parametern aufgerufen. Noch dazu: Da werden nicht die endgültigen Parameter übergeben; z.B. erwartet der Operator ja noch die Größe des zu allokierenden Datenbereichs in Bytes, die erst noch durch Compiler-generierten Text berechnet wird.

Re: [C++] Erläuterung zu einem Makrobefehl

Verfasst: 31.05.2011, 09:16
von Brainsmith
Ok, ich glaub, so langsam wirds klarer..
Danke nochmals.