Memory Leak mit std::map



  • Minimalstes Beispiel das ich erstellen konnte:

    #include <iostream>
    #include <map>
    #include <algorithm>
    
    #ifdef _DEBUG 
    #	define _CRTDBG_MAP_ALLOC
    #	include <stdlib.h>
    #	include <crtdbg.h>
    #endif /* _DEBUG */
    
    class Test
    {
    public:
    	void foo () {}
    };
    
    typedef std::map<int, Test*> TMap;
    
    void for_each_delete(std::pair<const int, Test*>& val)
    {
    	Test* p = val.second;
    	delete p;
    }
    
    void addPair(TMap& m, int key)
    {
    	m.insert(std::make_pair(key, new Test()));
    }
    
    int main()
    {
    	TMap myMap;
    	int key = 1;
    	addPair(myMap, key);
    
    	std::for_each(myMap.begin(), myMap.end(), for_each_delete);
    
    #ifdef _DEBUG
    	_CrtDumpMemoryLeaks();
    #endif /* _DEBUG */
    }
    
    Detected memory leaks!
    Dumping objects ->
    {140} normal block at 0x00AA5838, 24 bytes long.
     Data: < W   W   W      > A0 57 AA 00 A0 57 AA 00 A0 57 AA 00 01 00 00 00 
    {138} normal block at 0x00AA57A0, 24 bytes long.
     Data: <8X  8X  8X      > 38 58 AA 00 38 58 AA 00 38 58 AA 00 CD CD CD CD 
    Object dump complete.
    The program '[0x2C4] test.exe: Native' has exited with code 0 (0x0).
    

    Auch würde ich gerne fragen ob wer nen Link hat, wo genau steht wie man die Funktionsdefinition für for_each für die einzelnen Container genau nachlesen kann. Meine bisherige Suche in den einschlägigen Seiten war leider erfolglos.
    Danke.

    edit:
    Also nochmal zur Erklärung:
    delete p; wird aufgerufen aber das Memory Leak besteht trotzdem >_>. Es scheint keinen Unterschied zu machen ob ich das aufrufe oder nicht. In meinem "richtigen" Programm erzeugt der Aufruf genau dieses Konstrukts sogar eine Heap Verletzung. Nur warum?



  • Verwende mal einen eigenen Block für die Map, d.h.

    int main()
    {
        { // <- Blockanfang
            TMap myMap;
            int key = 1;
            addPair(myMap, key);
    
            std::for_each(myMap.begin(), myMap.end(), for_each_delete);
        } // <- Blockende
    
    #ifdef _DEBUG
        _CrtDumpMemoryLeaks();
    #endif /* _DEBUG */
    }
    

    Kommt jetzt in deinem Testprogramm immer noch ein Leak?
    Vorher wurde ja der Destruktor deiner Map noch nicht aufgerufen, und die einzelnen Key-Value-Pairs liegen ja auch auf dem Heap.



  • Okay, das Problem ist damit im Testprogramm erledigt. Habe ich nicht bedacht, mein Fehler. Sollte natürlich im eigenen Block sitzen.

    Im "richigen" Programm erzeugt das aber immer noch eine Heapverletzung. Die tritt ja normalerweise nur auf, wenn man ausserhalb der grenzen eines Containers geht, sprich sowas macht wie myVector[100] wo keine 100 Objekte existieren. Aber das ist ja hier nicht der Fall. Minimaler Code aus dem Ursprungsprogramm:

    // Call at program start, m_gs_mgr is an instance of GameStateManager
    m_gs_mgr.assignGameState(STATE_MENU, new GameStateMenu());
    
    // [.....]
    
    // Declaration of my the class
    class GameStateManager
    {
            typedef std::map<eGameState, IGameState*> TGameStateMap;
    
    public:
            GameStateManager();
            ~GameStateManager();
            void assignGameState(eGameState state, IGameState* ptr);
            void removeGameState(eGameState state, IGameState* ptr);
            void changeGameState(eGameState state);
    
    private:
            TGameStateMap m_states;
            eGameState m_current_state;
    };
    
    // callback for std::map
    void for_each_delete_map_entry(std::pair<eGameState, IGameState*> val)
    {
            IGameState* p = val.second;
            delete p; // <- crash is here, respectivly in crt0dat.c at line 732, ExitProcess(status)
    }
    
    // Destructor of the class should walk over all entries and free the objects on the heap which are still in the list.
    GameStateManager::~GameStateManager()
    {
            std::for_each(m_states.begin(), m_states.end(), for_each_delete_map_entry);
    }
    


  • du setzt in der for_each_delete die ungültigen Pointer auch nicht Null ....daher wird wohl was nen ungülltigen Pointer nehmen und dadurch corruption verursachen. Also: alle Pointer nullen oder alternativ: nach dem for_each die nun ungültigen Pointer aus der Map nehmen: myMap.clear();



  • Ne, das hilft nix. Weil das wird ja im Destruktor aufgerufen, also wird die komplette Map eh zerstört und interessiert ja auch keinen mehr. Es ist nur wichtig dass das GameState Objekt zerstört wird, weil das vorher mit new angelegt wird.
    Ich weiss mir nicht mehr zu helfen als euch den ganzen Code hinzuwerfen. So viel ist es ja nicht. Hab einige Zeit im Debugger verbracht und alle eventualitäten ausgeschlossen. Das Programm läuft an sich sauber, nur eben mit dem for_each gibts eine exception wenn ich objekte in der map habe.

    HEAP[NLSpaceGame-d.exe]: HEAP: Free Heap block b39a68 modified at b39df4 after it was freed

    #include "GameStateManager.hpp"
    
    void for_each_delete_map_entry(std::pair<const eGameState, IGameState*>& val)
    {
    	IGameState* p = val.second;
    	delete p; p = NULL;
    }
    
    GameStateManager::GameStateManager()
    : m_current_state(STATE_NOTHING)
    {	
    }
    
    GameStateManager::~GameStateManager()
    {
    	std::for_each(m_states.begin(), m_states.end(), for_each_delete_map_entry);
    	m_states.clear();
    }
    
    void GameStateManager::assignGameState( eGameState state, IGameState* ptr )
    {
    	TGameStateMap::iterator it = m_states.find(state);
    	if ( it == m_states.end () )
    	{
    		m_states.insert(std::make_pair(state, ptr) );
    		ptr->initGameState();
    	}	
    	else
    	{
    		IGameState* p = m_states[state];
    		delete p; p = NULL;
    		m_states[state] = ptr;
    		m_states[state]->initGameState();
    	}	
    }
    
    void GameStateManager::removeGameState( eGameState state )
    {
    	TGameStateMap::iterator it = m_states.find(state);
    	if ( it != m_states.end () )
    	{
    		IGameState* p = m_states[state];
    		delete p; p = NULL;
    		m_states.erase(it);
    	}
    }
    
    void GameStateManager::changeGameState( eGameState state )
    {
    	TGameStateMap::iterator it = m_states.find(state);
    	if ( it != m_states.end () )
    	{
    		if ( m_states.find(m_current_state) != m_states.end() )
    		{
    			m_states[m_current_state]->onLeaveGameState();
    		}		
    		m_current_state = state;
    		m_states[m_current_state]->onEnterGameState();
    	}
    }
    

    So wirds verwendet:

    void GameManager::run()
    {
    	// Set Vendor, open Log and create Directory
    	SystemController().setVendorName("Scorched Productions");
    	SystemController().setAppName("NIS");
    	SystemController().createUserDataDir();
    	SystemController().createLog(SystemController().getUserDataDir() + "NightInSpaceLog.html", MY_LOG_LEVEL, new NLHtmlWriter());
    
    	// Create Window
    	SystemController().getWindow().create(NLWindowSettings(1024,768, false, 32, 0, false, "Night in Space"));
    	SystemController().getWindow().setClearColor(NLCOLOR4F_BLACK);
    	SystemController().getWindow().connectSignal(NLBindRenderSlot(GameManager, onRender));
    	SystemController().getWindow().connectSignal(NLBindKeySlot(GameManager, onKey));
    
    	m_gs_mgr = new GameStateManager();
    	m_gs_mgr->assignGameState(STATE_MENU, new GameStateMenu()); // <- *
    	m_gs_mgr->changeGameState(STATE_MENU); // <- *
    
    	SystemController().getWindow().enterLoop();
    }
    

    Kommentiere ich die mit * markierten Zeilen läufts einwandfrei bzw wenn das for_each entferne. Ich verstehe halt absolut nicht warum.



  • egal



  • Clean+Rebuild?



  • Danke für eure Hilfe, habs gelöst. Waren viele Dinge, aber am Ende habe ich den Rat befolgt niemals Constructor und Destructor zu schreiben wenn mans nicht braucht. Das führte dazu, dass IGameState keinen destructor hatte bzw visual studio den "falsch" generiert hat, was dazu führte dass in GameStateMenu der Destructor nie aufgerufen wurde.
    Das explizite einfügen eines virtuellen Destruktors hat dann den in abgeleiteten Klassen auch aufgerufen und die Memory Leaks sind weg.
    Der Crash kam von einer Library die ich gestern eingebunden habe. Das muss ich extra untersuchen, ich hab den Code vorläufig entfernt und es geht wieder alles normal.



  • Noch ein Hinweis:
    Bei

    IGameState* p = val.second;
    delete p;
    p = NULL;
    

    setzt du nur die lokale Variable null, nicht den Zeiger in der Map, d.h. verwende entweder "IGameState* &p = val.second" (d.h. per Referenz) oder aber am Ende "val.second = 0".


Anmelden zum Antworten