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