Archive for the ‘Ponteiros’ 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

Anúncios

Functors – ponteiros para métodos

Me pediram para postar um exemplo de um sistema de eventos usando o assunto do post passado (ponteiros para funções e métodos), mas decidi que antes de fazer esse post, irei falar sobre functors – pois irei utilizar esse conceito no sistema de eventos.

Como falei no post anterior, um ponteiro para um método de uma classe tem uma sintaxe estranha e que pode facilmente complicar códigos, e além disso necessitamos de uma referência ao objeto do qual esse método será executado. Para facilita então o uso desses ponteiros especiais, vamos criar um conjunto simples de classes que encapsulam a funcionalidade de um ponteiro para método – esse conceito é chamado de Functor.

Para que a classe seja genérica, necessitaremos utilizar templates. Mas, como um template na prática gera uma classe diferente para cada tipo, necessitamos criar uma classe-base para que nossos functors possam ser tratados por código genérico. E aqui está o código da classe:


class Functor
{
public:
    virtual void operator() () = 0;
};

É uma classe abstrata bem simples – com apenas um operador virtual, o operador (). Isso nos permite fazer algo do tipo:


void funcao(Functor &func)
{
    // chama funcao encapsulada pelo functor
    func();
}

É logico q a classe Functor apenas nao resolve nada, então vamos olhar agora o código da classe que realmente faz a diferença – TemplateFunctor


template <class T>
class TemplateFunctor : public Functor
{
protected:
    // definindo o tipo do ponteiro
    typedef void (T::*metodo)();
    // o ponteiro para o objeto em questao
    T *_pObj;
    // o ponteiro para o método
    metodo _fpMetodo;
public:
    // construtor
    TemplateFunctor ( T *o, metodo m ) : _pObj(o),_fpMetodo(m) {}

    // operador de chamada de funcao ()
    void operator() ()
    {
        (_pObj->*_fpMetodo) ();
    }
};

Agora sim, temos um functor funcionando. A classe é relativamente simples – temos a definição do tipo do ponteiro, a definição dos ponteiros para o objeto e para o método, um construtor que garante a construção apropriada do functor, e o operador () q nos permite chamar o método através deste functor como se o objeto fosse o próprio método. Vamos exemplificar a seguir:


// uma classe qualquer
class A
{
public:
    A() {}

    void metodoQualquer() { /*faz algo*/ }
};

// nao podemos ter um functor sem um objeto
A a();
// criando o functor - passando objeto e método
TemplateFunctor<A> aFunc(&a, &A::metodoQualquer);
// chamando o método através do functor
aFunc();

Algo que provavelmente voce está se pergutando agora é – e se eu quiser métodos com outros tipos de retorno e parametros? Bom, para isso é necessária a criação de classes functor diferentes ou, então, planejar a sua própria functor para trabalhar com parameterização genérica (receber uma union, ou um ponteiro void). Porém, para um sistema de eventos, por exemplo, onde todos os métodos chamados terão a mesma assinatura, este tipo de functor facilita bastante a codificação.

Como sempre, postem suas dúvidas e sugestoes, estarei mais que disponivel para responde-las.