Archive for the ‘Gerenciamento de Memória’ Tag

Como detectar furos de memória no C++

Furos de memória são um dos tipos de erros mais frequentes no C++, e estão entre os mais dificeis de depurar. Existem várias bibliotecas de garbagge collector, e para aqueles que nao querem seus objetos gerenciados por um GC, existem várias bibliotecas que ajudam a detectar memória não liberada (Valgrind – por exemplo). Escrever seu proprio alocador é uma outra solução para ajudar a detectar esses erros.

Porém aqui irei mostrar um pequeno sistema que, em modo DEBUG, avisa se esquecemos de liberar algum ponteiro no final da execução. Básicamente o que iremos fazer é montar uma lista que, ao alocarmos memória, adicionamos na lista um bloco com informações dessa alocação, e removemos esse bloco da lista ao liberarmos a memória. Ao final da execução do programa, se a lista nao estiver vazia, teremos todas as informações sobre o que nao foi liberado. Vamos olhar entao o código do ‘memory.h’:


// soh queremos essa funcionalidade em modo DEBUG
#ifdef _DEBUG

// definimos uma funcao de log de memoria - aqui por exemplo usamos printf mesmo
#define memlog printf

// mais uma vez, alguns typedefs
typedef unsigned char byte;
typedef unsigned int dword;

// estrutura que guarda informações da alocação
typedef struct _alloc_info
{
   void *address;
   dword size;
   dword line;
   bool isArray;
   char *file;
} alloc_info_t;

// elemento da lista
typedef struct _alloc_node
{
   alloc_info_t data;
   _alloc_node *next;
} alloc_node_t;

// funcoes que vao fazer todo o trabalho sujo
void registerMemBlock ( const void *addr, const dword size, const char *file, const dword line, const bool isArray = false );
void removeMemBlock ( void *addr );
void checkMemoryLeaks ();

// override dos operadores new e delete
inline void* operator new ( size_t size, const char *file, const dword line )
{
   void *ptr = malloc(size);
   registerMemBlock ( ptr, (dword)size, file, line );
   return ptr;
}
inline void* operator new[] ( size_t size, const char *file, const dword line )
{
   void *ptr = malloc(size);
   registerMemBlock( ptr, (dword)size, file, line, true);
   return ptr;
}
inline void* operator delete ( void *p )
{
   removeMemBlock(p);
   free(p);
}
inline void* operator delete[] ( void *p )
{
   removeMemBlock(p);
   free(p);
}

#define debug_new  new(__FILE__,__LINE__)
#else
#define debug_new new
#endif

// um pequeno truque
//  ( nao funciona com a STL - memory.h deve ser incluido sempre DEPOIS de inclusao da STL )
#define new debug_new

Definimos neste código duas estruturas – que serão utilizadas para armazenar as informações sobre a alocação – e as tres funções que mantem a lista. Tambem redefinimos os operadores new e delete para q seja mais transparente utilizar esse sistema. Aquele pequeno truque no final faz com q todo new use nossa versao, bastando adicionar nosso .h. Esse truque, no entanto, dá problemas se vc incluir o memory.h antes de incluir qualquer biblioteca da STL (a STL se perde com o novo formato do new), entao cuidado com isso. Voce tambem pode criar uma outra macro para new, evitando esse problema.

Vamos agora olhar o código interno das funcoes (memory.cpp):


#ifdef _DEBUG

// cabeça-de-lista
alloc_node_t *_memlist = 0;

void registerMemBlock ( const void *addr, const dword size, const char *file, const dword line, const bool isArray = false )
{
   alloc_node_t *node = (alloc_node_t*)malloc ( sizeof(alloc_node_t) );

   // coloca informacoes no node
   alloc_info_t *data = &(node->data);
   data->address = (void*)addr;
   data->file = (char*)fle;
   data->line = (dword)line;
   data->size = (dword)size;
   data->isArray = (bool)isArray;

   // coloca na lista
   if ( !_memlist )
   {
      _memlist = node;
      node->next = 0;
   } else {
      node->next = _memlist;
      _memlist = node;
   }
}

void removeMemBlock ( void *addr )
{
   // pega o cabeça-de-lista
   alloc_node_t *last = 0, *node = _memlist;

   // verificacao de sanidade
   if ( !addr || !node ) return;

   // se addr e cabeça de lista, remove corretamente
   if ( node->data->address == addr )
   {
      _memlist = node->next;
      free(node);
      return;
   }

   // procura o nó correto
   do
   {
      last = node;
      node = node->next;
      if ( !node ) return;
   } while ( node->data->address != addr );

   // remove da lista
   last->next = node->next;
   free(node);
}

// neste aqui está a mágica
void checkMemoryLeaks ()
{
   dword totalWasted = 0;
   alloc_info_t *info;
   alloc_node_t *node = _memlist;

   // verifica se temos furo de memoria
   if ( node ) memlog ( "\nFURO DE MEMORIA!!\n" );
   else return;

   while ( node )
   {
      // simplifica acesso aos dados
      info = node->data;
      // adiciona ao contador de desperdicio
      totalWasted += info->size;
      // log
      memlog ( "File: '%s' Line: %d Address: 0x%p Unfreed: %d bytes %s\n",
                             info->file, info->line, info->address, info->size,
                             (info->isArray ? "(Array)" : "") );
      // remove o node e libera a memoria
      node = node->next;
      free(node->address);
      free(node);
   }
   memlog ( "\nTotal de memória desperdiçada: %d bytes", totalWasted );
}

#endif

Tá aí toda a mágica. as primeiras duas funções apenas mantêm a lista de alocações. a terceira função vasculha toda a lista e loga todas as alocações ainda existentes na lista – esta funcao eh para ser usada APENAS no final da execução do programa (seja com at_exit, ou como ultima linha de comando da main).

Este sistema é extremamente simples e rude – sistemas avançados de profiling e análise de execução sao mais apropriados mas, para sistemas mais simples e/ou de aprendizagem, este código é muito util.

Existe tambem outra questao – se voce escreveu seu próprio alocador de memória, o controle já pode ir incluido diretamente na lógica desse alocador, nao sendo necessario entao este aqui. Em outro post irei explicar como fazer alguns tipos diferentes de alocadores de memória que podem nao só proteger o seu programa de furos de memória, mas também aumentar sensivelmente a performance da alocação e liberação