Recherche de fuites mémoire en C++

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_DETECTORH #define _LEAK_DETECTORH

#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&amp; _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&amp; _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&lt;void*, TBlock&gt; TBlockMap;

TBlockMap          m_Blocks;
std::stack&lt;TBlock&gt; 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):
My Rant on C++’s operator new