[C++] Hoe vind ik een memory leak in VS2008?

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • r0_
  • Registratie: Augustus 2006
  • Laatst online: 22-09 11:53
Tweakers,

Ik heb denk ik een relatief simpele vraag. Ik ben bezig met het ontwikkelen van een programma in VS 2008 (in C++). Op verschillende plaatsen gebruik het keyword "new". Het logische gevolg is natuurlijk dat ik dan ook "delete" moet gaan gebruiken om memory leaks te voorkomen. Dit snap ik opzich wel alleen maar omdat ik nog lerende ben wil ik ook weten of alles goed wordt opgeruimd. Oftewel, ik moet weten of er geen memory leaks overblijven.
Ik weet dat het via de debugger zou moeten kunnen. Het is mij alleen onduidelijk hoe dit precies gedaan moet worden. Ik heb vanzelfsprekend al wel gezocht op google en MSDN maar ik kan helaas geen eenduidige "How To:" vinden. Moet ook bekennen dat ik door de bomen het bos niet meer zie....

Ik heb dit wel gevonden maar weet niet goed hoe ik het moet toepassen. (lees: waar moet ik in mijn code moet neerzetten )
C++:
1
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);


en bij de properties van de m'n project:
Configuration Properties->C/C++->preprocessor->Preprocessor Definitions: _DEBUG


Wie o wie kan mij een handje helpen?

edit::
Heb info gevonden, heb alleen nog 1 vraag daarover.
Mijn info komt hier vandaan:

MSDN

ik moet dus dit includen:
C++:
1
2
3
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>


Het probleem is dat ik hier geen informatie krijg over waar de leak zit.
Daar staat ook iets over op dezelfde MSDN pagina:
If you do not use the #define _CRTDBG_MAPALLOC statement, the memory leak dump would look like this:

Detected memory leaks!
Dumping objects ->
{18} normal block at 0x00780E80, 64 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
Bij mij ziet het er dus zo uit. Ik gebruik alleen WEL de betreffende define. Dus ik snap niet hoe dat komt. Wie weet het wel? :9

[ Voor 27% gewijzigd door r0_ op 16-12-2008 11:31 ]


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
De link nog eens doorlezen; die gaat over malloc/free.

In C++ zijn memory leaks eigenlijk geen probleem meer. Voor elke new zou het triviaal moeten zijn om de bijbehorende delete te vinden. new in constructor, delete in destructor bijvoorbeeld. Of new gevolgd door assignment aan smart pointer, dan staat de delete in de destructor van de smart pointer.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

In jouw post staan 2 spellingen: MAP_ALLOC met en zonder underscore, check of je dat exact goed hebt. Tevens moet je die define en de crtdbg header ALS ALLEREERSTE includen in ELKE sourcefile om er zeker van te zijn dat je ook echt alle allocations mapped.

-niks-


Acties:
  • 0 Henk 'm!

  • r0_
  • Registratie: Augustus 2006
  • Laatst online: 22-09 11:53
MLM schreef op dinsdag 16 december 2008 @ 11:59:
In jouw post staan 2 spellingen: MAP_ALLOC met en zonder underscore, check of je dat exact goed hebt. Tevens moet je die define en de crtdbg header ALS ALLEREERSTE includen in ELKE sourcefile om er zeker van te zijn dat je ook echt alle allocations mapped.
Die twee verschillende spellingen had ik ook al ontdekt. Het lijkt er echter op dat het een foutje is op de MSDN page. Ze zeggen namelijk eerst dat ik dit moet define-en:

#define _CRTDBG_MAP_ALLOC

en verderop staat:

If you do not use the #define _CRTDBG_MAPALLOC statement, the memory leak dump would look like this:[...]

Beetje raar dus. Maar heb ik ze beide geprobeerd, en ze geven beide hetzelfde (niet gewenste) resultaat.

Verder heb ik het nu als volgt gedaan:
in main.cpp staat:

C++:
1
2
3
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>


Daarna heb ik in iedere .cpp file dit gezet:
C++:
1
2
#include <stdlib.h>
#include <crtdbg.h>


In main.cpp roep ik dan als laatst deze functie aan:
C++:
1
_CrtDumpMemoryLeaks();


Is dat zoals het bedoeld is?

offtopic: Hoop niet dat het een teveel "ik heb dit gedaan, het werkt niet, wat nu?" topic gaat worden....

Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

nee, in C(++) is elke C/CPP file een aparte translation unit, dus elke source file (dus niet header file) moet die define hebben ;)

in VS kan je eventueel in je project settings er voor zorgen dat ie altijd defined is, moet je bij de compiler tool de preprocessor definitions bijwerken.

de _CrtDumpMemoryLeaks() zoe je ideaal gezien inderdaad als laatste uitvoeren, eventueel zelfs na je main functie (je kan objecten met allocaties erin op de stack van main hebben immers), dit zou volgens mij een leak moeten aageven terwijl die er eigenlijk niet is :)
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRTDBG_MAP_ALLOC 
#include <stdlib.h> 
#include <crtdbg.h>

class voorbeeldobject
{
  int *m_allocatie;
public:
  voorbeeldobject() { m_allocatie = new int; }
  ~voorbeeldobject() { delete m_allocatie; }
};

int main(int, char **)
{
  voorbeeldobject obj;
  _CrtDumpMemoryLeaks();
}

ervanuitgaande dat je geen static duration objecten hebt met allocaties, kan je dat simpel oplossen met
C++:
1
2
3
4
5
6
7
int main(int, char **)
{
  {
    voorbeeldobj obj;
  }
  _CrtDumpMemoryLeaks();
}

[ Voor 73% gewijzigd door MLM op 16-12-2008 12:47 ]

-niks-


Acties:
  • 0 Henk 'm!

  • Haan
  • Registratie: Februari 2004
  • Laatst online: 09:20

Haan

dotnetter

Heb je het trouwens sowieso niet over VC++? (aangezien je het hebt over VS2008). Dan heb je toch ook de standaard garbadge collection van .Net?

Kater? Eerst water, de rest komt later


Acties:
  • 0 Henk 'm!

  • Snake
  • Registratie: Juli 2005
  • Laatst online: 07-03-2024

Snake

Los Angeles, CA, USA

Haan schreef op dinsdag 16 december 2008 @ 12:42:
Heb je het trouwens sowieso niet over VC++? (aangezien je het hebt over VS2008). Dan heb je toch ook de standaard garbadge collection van .Net?
1) gan moet je gcnew gebruiken (garbage collector new).
2) dat werkt alleen voor C++.NET apps.

Going for adventure, lots of sun and a convertible! | GMT-8


Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

Managed C++ heb je inderdaad OOK de managed object heap waar je op kan alloceren (__gcnew), maar ik denk niet dat de TS zich daar mee bezig houd ;)

VS2008 is ook geen programmeertaal, het is een IDE waarin je kan programmeren in verschillende talen (bijvoorbeeld, C# (managed), C++ (managed), C++ (native), VB.NET), de statement dat VS2008 garbage collection heeft slaat als een hamer op een varken (of was dat niet het gezegde :P)

-niks-


Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

MSalters schreef op dinsdag 16 december 2008 @ 11:40:
De link nog eens doorlezen; die gaat over malloc/free.

In C++ zijn memory leaks eigenlijk geen probleem meer. Voor elke new zou het triviaal moeten zijn om de bijbehorende delete te vinden. new in constructor, delete in destructor bijvoorbeeld. Of new gevolgd door assignment aan smart pointer, dan staat de delete in de destructor van de smart pointer.
Dat is wel enigzins kort door de bocht, je kan ook nog een exception hebben die roet in het eten gooit, deze class gaat bijvoorbeeld altijd leaken:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class leakendobject
{
  int *m_allocatie;

  void init()
  {
    throw "initialisatie mislukt!";
  }
public:
  leakendobject()
  {
    m_allocatie = new int;
    init();
  }

  ~leakendobject()
  {
    delete m_allocatie;
  }
};

en daarnaast, zelfs als jij altijd netjes smart_pointer of auto_ptr of een dergelijke constructie gebruikt betekent niet automatisch dat de rest van de wereld dat ook doet, en als jij een library hebt die dat niet doet, heb je best kans dat het alsnog fout gaat :)

-niks-


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
In je voorbeeld gooit je constructor ook een exception. Dat is iets wat je natuurlijk beter kunt voorkomen

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

rwb schreef op dinsdag 16 december 2008 @ 13:05:
In je voorbeeld gooit je constructor ook een exception. Dat is iets wat je natuurlijk beter kunt voorkomen
Voor zover ik weet is het juist een design principle om een object wat niet geconstruct kan worden een exception te laten raisen. Of heeft elk object in jouw code een flag waaraan je kan zien of de constructie gelukt is, en na elke new doe je daar een check op, en doe je een delete waar nodig?
Dan heb ik liever exceptions ;)
Als er een doel voor exceptions was, was het wel om constructors te laten throwen, omdat die geen return value hebben...

Het is WEL een goed idee om er voor te zorgen dat destructors nooit raisen, dat is wel een serieus probleem!

-niks-


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-09 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

Sowieso ga je met MAP_ALLOC geen new calls tracen, zoals MSalters al zei. Die doet alleen malloc(), die waarschijnlijk wordt aangeroepen vanuit de standaard operator new() implementatie (heb je dus niets aan). Wat je wel kunt doen is zorgen dat je allocaties deterministisch zijn elke run van je applicatie, zodat de leakende allocaties altijd hetzelfde id hebben. Vervolgens kun je met _CrtSetBreakAlloc() ervoor zorgen dat er een breakpoint getriggered wordt bij dat allocation id, zodat je de context van die allocatie kunt inspecteren met de debugger.
MLM schreef op dinsdag 16 december 2008 @ 12:51:
Managed C++ heb je inderdaad OOK de managed object heap waar je op kan alloceren (__gcnew), maar ik denk niet dat de TS zich daar mee bezig houd ;)
Managed C++ (2002, 2003) is geen C++/CLI (2005 en later). __gcnew gebruik je idd in Managed C++, maar gewoon gcnew in C++/CLI ;)

[ Voor 3% gewijzigd door .oisyn op 16-12-2008 13:20 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • r0_
  • Registratie: Augustus 2006
  • Laatst online: 22-09 11:53
Zo zeg, dat gaat hard met de replys!

MLM zijn vermoeden is inderdaad juist. Veel zaken waar jullie over praten heb ik nog geen kaas van gegeten. De hoofdstukken van exceptions en auto_ptr ed. heb ik nog niet doorgewerkt. Daarnaast werk ik in VS 2008 met VC++ 2008 ( als je dat zo kan zeggen).
Verder ben ik zelfs nog op het niveau dat ik niet eens zeker weet wat het verschil is tussen managed C++ en native C++. Is het zo dat managed C++ .NET is? En dat native C++ 'gewoon' is? Als dat het geval is gebruik ik native C++.

Helaas heb ik nog geen oplossing op mijn oorspronkelijke probleem. Ik krijg wel te zien dat er leaks zijn maar er staat nog niet aangegeven waar. Ik heb nu in iedere .cpp dit bovenaan staan:
C++:
1
2
3
#define _CRTDBG_MAP_ALLOC      // Dus met de underscore. Weet ook niet of het nu echt verschil maakt...
#include <stdlib.h>
#include <crtdbg.h>


Er is echter wel een vreemde eend in de bijt. Ik heb een template klasse waar de implementaties in de .h staan omdat dit moet bij een template klasse. De implementatie file bevat dus eigenlijk niks. Daarom heb ik bovenstaande code in de header file geplaats van die betreffende template klasse.

Ook is het mij ook nog niet helemaal duidelijk of dit nu ook vereist is:
Configuration Properties->C/C++->preprocessor->Preprocessor Definitions: _DEBUG
of is dat niet nodig?

edit:
Ik heb hier ook nog wat info gevonden:
http://www.codeguru.com/forum/showthread.php?t=312742

bij de vraag "Q: What is the effect of using '_CRTDBG_MAP_ALLOC' on the C++ 'new' and 'delete' operators?"
staat dat MAP_ALLOC ook met new/delete zou moeten kunnen werken als ik het goed begrijp. Dat verklaart in ieder geval wel waarom ik wel resultaten krijg, alleen nog niet waarom er niet staat waar de leaks zitten.
Er staat ook: "This flag is only available when the '_DEBUG' flag has been defined in the application."
Dat lijkt een antwoord te zijn op mijn vraag of dit goed is:
Configuration Properties->C/C++->preprocessor->Preprocessor Definitions: _DEBUG
Ik weet alleen nog niet helemaal zeker of _DEBUG dan inderdaad op de goede plek staat.

[ Voor 21% gewijzigd door r0_ op 16-12-2008 13:37 ]


Acties:
  • 0 Henk 'm!

  • writser
  • Registratie: Mei 2000
  • Laatst online: 25-09 18:39
rwb schreef op dinsdag 16 december 2008 @ 13:05:
In je voorbeeld gooit je constructor ook een exception. Dat is iets wat je natuurlijk beter kunt voorkomen
Het probleem is niet dat je constructor een exception gooit, maar dat je constructor onveilig is. Zie http://www.parashift.com/.../exceptions.html#faq-17.4 voor hoe het zou moeten.
# Use of raw (as opposed to smart) pointers: This is actually just a special case of non-RAII coding, but I'm calling it out because it is so common. The result of using raw pointers is, as above, lots of extra try/catch blocks whose only purpose in life is to delete an object then re-throw the exception.

Onvoorstelbaar!


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
MLM schreef op dinsdag 16 december 2008 @ 13:10:
[...]
Het is WEL een goed idee om er voor te zorgen dat destructors nooit raisen, dat is wel een serieus probleem!
Ik was inderdaad even in de war met destructors, bij een constructor moet je alleen zorgen dat je object geen resources meer in gebruik heeft als je een exception throwt, aangezien de destructor niet word aangeroepen

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

writser schreef op dinsdag 16 december 2008 @ 13:31:
[...]

Het probleem is niet dat je constructor een exception gooit, maar dat je constructor onveilig is. Zie http://www.parashift.com/.../exceptions.html#faq-17.4 voor hoe het zou moeten.


[...]
Klopt, en dat weet ik ook wel, ik gaf expliciet een fout voorbeeld, zoals erboven ook staat ;)

Maar om de TS (die zo te zien bezig is met leren van C++) gelijk door te verwijzen naar boost voor een pointer (danwel auto_ptr, maar die is nogal apart qua assignment), lijkt me ietwat overdreven.

Ik vind zelf dat het juist een goed idee dat als je C++ leert dat je begint met "echte" pointers en "echte" strings, zodat je toch weet hoe het achter de schermen werkt. Daarna kan je verder met een smart_ptr en een std::string, en weet je waarom het werkt zoals het werkt ;)

Wat de TS misschien wel kan doen is, zoals .oisyn zegt, breaken op een allocatie ID en/of een tool gebruiken die wel new calls voorziet van een __FILE__/__LINE__ aanhangsel. (Disclaimer: Veel van deze "handige" tooltjes die je op internet vind zijn of niet platform-onafhankelijk of werken niet altijd, of eisen dat je je code herschrijft :P)

@r0_: die _DEBUG hoort inderdaad bij de preprocessor definitions, en staat (als het goed is) standaard ook al gedefinieerd als je een project aanmaakt

[ Voor 17% gewijzigd door MLM op 16-12-2008 13:52 ]

-niks-


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-09 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

Het probleem van new is helaas dat je niet zomaar een macro kunt maken die de __FILE__ en __LINE__ informatie eraan toevoegt. Je zult dan idd of een andere constructie moeten gebruiken (zoals DBG_NEW(MyClass(4)) ipv new MyClass(4)), of gebruik maken van de debug informatie en je stack om te achterhalen waar de call vandaan kwam (het voordeel van die laatste is echter wel dat je een veel uitgebreidere stacktrace kunt laten zien)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • writser
  • Registratie: Mei 2000
  • Laatst online: 25-09 18:39
Maar is dat nou nuttig voor de topicstarter?
Dit snap ik opzich wel alleen maar omdat ik nog lerende ben wil ik ook weten of alles goed wordt opgeruimd. Oftewel, ik moet weten of er geen memory leaks overblijven.
Het lijkt me meer een geval van symptoonbestrijding. Je kan ook een goed boek doornemen en jezelf een goede programmeerstijl aanleren (RAII) en dan is er zoals MSalters al zegt niks aan de hand. Lijkt me op de langere termijn een betere oplossing.

Onvoorstelbaar!


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

MLM schreef op dinsdag 16 december 2008 @ 13:47:
Maar om de TS (die zo te zien bezig is met leren van C++) gelijk door te verwijzen naar boost voor een pointer (danwel auto_ptr, maar die is nogal apart qua assignment), lijkt me ietwat overdreven.
Dat heb je toch nodig als je ooit meer dan een (resource) allocatie wilt doen in een class. Dat moet je wrappen in een object als je raii wilt blijven aanhouden.

(en C++ zonder raii is maar stom, dat zou ik er vrij vroeg in stampen :P )

[ Voor 7% gewijzigd door Zoijar op 16-12-2008 14:37 ]


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-09 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

writser schreef op dinsdag 16 december 2008 @ 14:11:
Maar is dat nou nuttig voor de topicstarter?
Was jouw vorige reactie nuttig voor de TS?

Maar ja, de mijne was nuttig. Wellicht niet in z'n huidige vorm, maar wel:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define _CRTDBG_MAP_ALLOC 
#include <iostream>
#include <cstdlib> 
#include <crtdbg.h>

#ifdef _DEBUG
void * operator new(size_t s, const char * file, size_t line)
{
    return _malloc_dbg(s, _NORMAL_BLOCK, file, line);
}

void operator delete(void * ptr, const char * file, size_t line)
{
    _free_dbg(ptr, _NORMAL_BLOCK);
}

#define TRACK_NEW(x) (new(__FILE__, __LINE__) x)

#else

#define TRACK_NEW(x) (new x)

#endif //_DEBUG

int main()
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF);

    int * i = TRACK_NEW(int(34));
    std::cout << *i << std::endl;

    _CrtDumpMemoryLeaks();
}

Moet de TS alleen nog even van al z'n new operators omzetten naar TRACK_NEW()

Detected memory leaks!
Dumping objects ->
c:\...\main.cpp(29) : {191} normal block at 0x00443888, 4 bytes long.
 Data: <"   > 22 00 00 00 
Object dump complete.

[ Voor 12% gewijzigd door .oisyn op 16-12-2008 14:47 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • r0_
  • Registratie: Augustus 2006
  • Laatst online: 22-09 11:53
writser schreef op dinsdag 16 december 2008 @ 14:11:
Maar is dat nou nuttig voor de topicstarter?

[...]

Het lijkt me meer een geval van symptoonbestrijding. Je kan ook een goed boek doornemen en jezelf een goede programmeerstijl aanleren (RAII) en dan is er zoals MSalters al zegt niks aan de hand. Lijkt me op de langere termijn een betere oplossing.
Je hebt gelijk. Ik waardeer de antwoorden/suggesties van de 'experts' :9 ten zeerste alleen zijn ze voor mij niet altijd even bruikbaar. Ik snap ze deels wel, maar deels ook niet. Wat tot gevolg heeft dat ik nog geen oplossing heb. En om steeds te vragen wat er nu wordt bedoeld is denk ik ook niet helemaal de bedoeling want dan wordt het een beetje langdradig allemaal.

Verder heb ik wel een goed boek (Aan de slag met C++) Alleen kan dat boek mij niet vertellen hoe ik kan controleren of ik alles goed opruim. Vandaar mijn oorspronkelijke vraag hoe kan ik in VS2008 zien of, en waar er nog leaks zitten.

edit: Zie dat .oisyn zijn best heeft gedaan :) Eens kijken of ik daar wat mee kan. Het is wel wat meer dan ik had verwacht. Dacht dat het een simpel vinkje zou zijn in VS2008, helaas gaat die vlieger niet op....

[ Voor 9% gewijzigd door r0_ op 16-12-2008 14:47 ]

Pagina: 1