Chez ouam

/home/jmfrouin

View on GitHub
25 January 2013

Recherche de fuites mémoire en C++

by Jean-Michel Frouin

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</a>

tags: