cpp home
Recherche de fuites mémoire en C++
Posted by Jean-Michel Frouin on .Introduction
Pour pouvoir détecter les fuites mémoires, il faut pouvoir garder une trace de toutes les allocations mémoires qui sont faites durant toute l'exécution du programme.
En C++ toutes les allocations de mémoires ce font en utilisant l'opérateur new ou new[] et les libérations de mémoire en utilisant l'opérateur delete ou delete[].
Un moyen simple de garder une trace des allocations ou libérations de mémoire est donc de surcharger l'opérateur new et delete.
Surcharge des opérateurs mémoires du C++
Dans cette implémentation, chaque opérateur fait appel à une méthode d'une classe CMemoryManager qui gère l'ensemble des allocations / libérations. La classe CMemoryManager est aussi chargée de construire le rapport de fuites mémoires à la fin de l'exécution du programme.
#ifdef LEAKS #ifndef _LEAK_DETECTOR_H_ #define _LEAK_DETECTOR_H_ #include "memory_manager.h" extern CMemoryManager g_mm;
Au passage on note, que le singleton n'est pas toujours obligatoire, même si l'on n'utilise qu'une instance d'un objet, du moment que l'on sait ce que l'on fait. Ici l'instance g_mm est créée dans la fonction main de mon programme, et n'est ensuite utilisée, que en la rattrapant au moment de la résolution, via extern.
#ifdef LEAKS /*! * @brief new operator surcharge */ inline void* operator new(std::size_t Size, const char* File, int Line) { return g_mm.Allocate(Size, File, Line, false); } /*! * @brief new[] operator surcharge */ inline void* operator new[](std::size_t Size, const char* File, int Line) { return g_mm.Allocate(Size, File, Line, true); } /*! * @brief delete operator surcharge */ inline void operator delete(void* Ptr) { g_mm.Free(Ptr, false); } /*! * @brief delete[] operator surcharge */ inline void operator delete(void* Ptr, const char* File, int Line) { g_mm.NextDelete(File, Line); g_mm.Free(Ptr, false); } inline void operator delete[](void* Ptr) { g_mm.Free(Ptr, true); } inline void operator delete[](void* Ptr, const char* File, int Line) { g_mm.NextDelete(File, Line); g_mm.Free(Ptr, true); } #endif // _LEAK_DETECTOR_H__
Ici, on surcharge les opérateurs new et delete en inline. Du coup à la compilation, le compilateur remplacera les appels à ces méthodes, par la définition de la méthode.
#undef delete #ifndef new #define new new(__FILE__, __LINE__) #define delete g_mm.NextDelete(__FILE__, __LINE__), delete #endif #endif // LEAKS
L'utilisation des macros de pré-compilation (__FILE__ et __LINE__) dans la redéfinition des opérateurs, permet de localiser l'emplacement (dans les fichiers) d'une allocation ou d'une libération de mémoire.
Bref, tout ce fichier n'est qu'une soupe pour le pré-compilateur :)
Déclaration de la classe CMemoryManager :
#ifndef __MEMORY_MANAGER_H__ #define __MEMORY_MANAGER_H__ #include <fstream> #include <map> #include <stack> #include <string> #include <def.h> #include "log.h" /*! * @brief Memory manager, in fact for the moment it's only a leak detector. */ class CMemoryManager : public ILog { public : /*! * @brief Default constructor. */ CMemoryManager(); /*! * @brief Destructor. */ ~CMemoryManager(); /*! * @brief Do memory allocation. * @param _size Size to allocate. * @param _file Store the file where delete is did. * @param _line Store the line where delete is did. * @param _array Pointer point on array type ?. */ void* Allocate(std::size_t _size, const std::string& _file, int _line, bool _array); /*! * @brief Free memory. * @param _ptr Pointer on memory zone to free. * @param _array Pointer point on array type ?. */ void Free(void* _ptr, bool _array); /*! * @brief Default constructor. * @param _file Store the file where delete is did. * @param _line Store the line where delete is did. */ void NextDelete(const std::string& _file, int _line); /*! * @brief From ILog */ void Report(); private: /*! * @brief Memory stucture. */ struct TBlock { std::size_t Size; std::string File; unsigned int Line; bool Array; static std::size_t Total; }; typedef std::map<void*, TBlock> TBlockMap; TBlockMap m_Blocks; std::stack<TBlock> m_DeleteStack; }; #endif // __MEMORY_MANAGER_H__
Définition de la classe CMemoryManager :
#include <iomanip> #include <sstream> #include <iostream> #include "memory_manager.h" std::size_t CMemoryManager::TBlock::Total = 0; CMemoryManager::CMemoryManager() { m_File.open("_memoryleaks.log"); if (!m_File) { std::cout << "Erreur : Cannot open m_File" << std::endl; } m_File << "====================================================================================" << std::endl; m_File << " MemoryManager v" << VERSION_MEMORY_MANAGER << " - Report (Compiled on " << __DATE__ << " @ " << __TIME__ << ")" << std::endl; m_File << "====================================================================================" << std::endl << std::endl; } CMemoryManager::~CMemoryManager() { std::cout << "[DEBUG] [CMemoryManager] ~CMemoryManager()" << std::endl; if (m_Blocks.empty()) { m_File << std::endl; m_File << "====================================================================================" << std::endl; m_File << " No leak detected, congratulations ! " << std::endl; m_File << "====================================================================================" << std::endl << std::endl; } else { m_File << std::endl; m_File << "====================================================================================" << std::endl; m_File << " Oops... Some leaks have been detected " << std::endl; m_File << "====================================================================================" << std::endl << std::endl; m_File << std::endl; Report(); } } void CMemoryManager::Report() { std::cout << "[DEBUG] [CMemoryManager] ReportLeaks()" << std::endl; std::size_t TotalSize = 0; for (TBlockMap::iterator i = m_Blocks.begin(); i != m_Blocks.end(); ++i) { TotalSize += i->second.Size; m_File << "-> 0x" << i->first << " | " << std::setw(7) << std::setfill(' ') << static_cast<int>(i->second.Size) << " bytes" << " | " << i->second.File << " (" << i->second.Line << ")" << std::endl; free(i->first); } m_File << std::endl << std::endl << "-- " << static_cast<int>(m_Blocks.size()) << " blocs not empty, " << static_cast<int>(TotalSize) << " bytes --" << std::endl; } void* CMemoryManager::Allocate(std::size_t _size, const std::string& _file, int _line, bool _array) { void* Ptr = malloc(_size); TBlock NewBlock; NewBlock.Size = _size; NewBlock.File = _file; NewBlock.Line = _line; NewBlock.Array = _array; NewBlock.Total += _size; m_Blocks[Ptr] = NewBlock; m_File << "+++" << " " << Ptr << " " << static_cast<int>(NewBlock.Size) << " " << NewBlock.Total << " " << NewBlock.File << " " << NewBlock.Line << std::endl; return Ptr; } void CMemoryManager::Free(void* _ptr, bool _array) { TBlockMap::iterator It = m_Blocks.find(_ptr); std::cout << "[DEBUG] [CMemoryManager] Free(" << _ptr << ") " << std::endl; if (It == m_Blocks.end()) { free(_ptr); return; } if (It->second.Array != _array) { m_File << "-- ERREUR | 0x" << _ptr << " @ " << It->second.File << " Line : " << It->second.Line << std::endl; return; } if(!m_DeleteStack.empty()) { m_File << "---" << " " << _ptr << " " << static_cast<int>(It->second.Size) << " " << m_DeleteStack.top().File << " " << m_DeleteStack.top().Line << std::endl; } else { m_File << "---" << " " << _ptr << " " << static_cast<int>(It->second.Size) << std::endl; } m_Blocks.erase(It); if(!m_DeleteStack.empty()) { m_DeleteStack.pop(); } free(_ptr); } void CMemoryManager::NextDelete(const std::string& _file, int _line) { TBlock Delete; Delete.File = _file; Delete.Line = _line; m_DeleteStack.push(Delete); }
Article ayant été lu pendant la mise au point de ce détecteur de fuites mémoires (leaks):
<a href="http://www.scs.stanford.edu/~dm/home/papers/c++-new.html" target"=_blank">My Rant on C++'s operator new