Compartilhar via


Localizar vazamentos de memória com a biblioteca CRT

Vazamentos de memória estão entre os bugs mais sutis e difíceis de detectar em aplicativos C/C++. Vazamentos de memória resultam da falha ao desalocar corretamente a memória alocada anteriormente. Um vazamento de memória pequeno não pode ser observado no início, mas ao longo do tempo pode causar os sintomas que variam de desempenho reduzido a falhas quando o aplicativo é executado sem memória. Um aplicativo de escape que usa toda a memória disponível pode causar a falha de outro aplicativo, criando a confusão a respeito de que o aplicativo é responsável. Até mesmo vazamentos de memória inofensivos podem ser indicar outros problemas que devem ser corrigidos.

O depurador do Visual Studio e a CRT (Biblioteca em Tempo de Execução) do C podem ajudá-lo a detectar e identificar vazamentos de memória.

Habilitar a detecção de vazamento de memória

As principais ferramentas para detectar vazamentos de memória são o depurador C/C++ e as funções de heap de depuração CRT.

Para habilitar todas as funções de heap de depuração, inclua as seguintes instruções em seu programa C++ na seguinte ordem:

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

A declaração de #define mapeia uma versão de base de funções heap de CRT para a versão de depuração correspondente. Se você omitir a instrução #define, o despejo de vazamento de memória será menos detalhado.

Incluindo crtdbg.h mapeia as funções e para suas versões de depuração, e , _malloc_dbg que rastreiam a alocação de memória e free_free_dbga malloc localização de distribuição. Esse mapeamento ocorre apenas em compilações de depuração, que tem _DEBUG. Compilações lançadas usam as funções malloc e free.

Depois de habilitar as funções de heap de depuração usando as instruções anteriores, faça uma chamada para antes de um ponto de saída do aplicativo para _CrtDumpMemoryLeaks exibir um relatório de vazamento de memória quando o aplicativo for encerrado.

_CrtDumpMemoryLeaks();

Se o aplicativo tiver várias saídas, você não precisará colocar manualmente _CrtDumpMemoryLeaks em cada ponto de saída. Para fazer com que uma chamada automática para _CrtDumpMemoryLeaks em cada ponto de saída, faça uma chamada para _CrtSetDbgFlag no início do aplicativo com os campos de bit mostrados aqui:

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

Por padrão, a saída _CrtDumpMemoryLeaks emite o relatório de vazamento de memória para o painel Depurar da janela de Saída. Se você usar uma biblioteca, a biblioteca poderá redefinir a saída para outro local.

Você pode usar _CrtSetReportMode para redirecionar o relatório para outro local ou voltar para a janela Saída, conforme mostrado aqui:

_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG );

O exemplo a seguir mostra um vazamento de memória simples e exibe informações de vazamento de memória usando _CrtDumpMemoryLeaks();o .

// debug_malloc.cpp
// compile by using: cl /EHsc /W4 /D_DEBUG /MDd debug_malloc.cpp
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>

int main()
{
    std::cout << "Hello World!\n";

    int* x = (int*)malloc(sizeof(int));

    *x = 7;

    printf("%d\n", *x);

    x = (int*)calloc(3, sizeof(int));
    x[0] = 7;
    x[1] = 77;
    x[2] = 777;

    printf("%d %d %d\n", x[0], x[1], x[2]);

    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); 
    _CrtDumpMemoryLeaks();
}

Interpretando o relatório de vazamento de memória

Se o seu aplicativo não definir _CRTDBG_MAP_ALLOC, _CrtDumpMemoryLeaks exibirá um relatório de vazamento de memória parecido com este:

Detected memory leaks!
Dumping objects ->
{18} normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

Se o seu aplicativo definir _CRTDBG_MAP_ALLOC, o relatório de vazamento de memória se parecerá com esse:

Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\leaktest\leaktest.cpp(20) : {18}
normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

O segundo relatório mostra o nome do arquivo e número da linha no qual a memória vazada é atribuída primeiro.

Se você definir ou não _CRTDBG_MAP_ALLOC, o relatório de perda de memória exibirá:

  • O número de alocação de memória, que é 18 nesse exemplo
  • O tipo de bloco, normal no exemplo.
  • O número de alocação de memória hexadecimal, 0x00780E80 nesse exemplo.
  • O tamanho do bloco, 64 bytes neste exemplo.
  • Os primeiros 16 bytes de dados no bloco, no formulário hexadecimal.

Os tipos de bloco de memória são normais, cliente ou CRT. Um bloco normal é a memória comum atribuída pelo seu programa. Um bloco de cliente é um tipo especial de bloco de memória usado por programas MFC para os objetos que exigem um destruidor. O operador MFC new cria um bloco normal ou um bloco de cliente, como apropriado para o objeto que estiver sendo criado.

Um bloco de CRT é atribuído pela biblioteca de CRT para seu próprio uso. A biblioteca CRT lida com a desalocação desses blocos, portanto, os blocos CRT não aparecerão no relatório de perda de memória, a menos que haja sérios problemas com a biblioteca CRT.

Há dois tipos de outros blocos de memória que nunca aparecem nos relatórios de escape de memória. Um bloco livre é a memória que foi liberada, portanto, por definição, não é vazado. Um bloco ignorar é a memória que você marcou explicitamente para excluir do relatório de vazamento de memória.

As técnicas anteriores identificam vazamentos de memória para memória alocada usando a função malloc CRT padrão. No entanto, se o programa alocar memória usando o operador C++ new, você poderá ver apenas o nome do arquivo e o número operator new de linha em que as chamadas _malloc_dbg no relatório de perda de memória. Para criar um relatório de perda de memória mais útil, você pode escrever uma macro como a seguinte para relatar a linha que fez a alocação:

#ifdef _DEBUG
    #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
    // Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
    // allocations to be of _CLIENT_BLOCK type
#else
    #define DBG_NEW new
#endif

Agora você pode substituir o operador new usando a macro DBG_NEW em seu código. Em compilações de depuração, DBG_NEW usa uma sobrecarga de global operator new que usa parâmetros extras para o tipo de bloco, arquivo e número de linha. A sobrecarga de new chama _malloc_dbg para registrar as informações extras. Os relatórios de perda de memória mostram o nome do arquivo e o número de linha em que os objetos vazados foram alocados. Os builds de versão ainda usam o padrão new. Veja um exemplo da técnica:

// debug_new.cpp
// compile by using: cl /EHsc /W4 /D_DEBUG /MDd debug_new.cpp
#define _CRTDBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>

#ifdef _DEBUG
    #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
    // Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
    // allocations to be of _CLIENT_BLOCK type
#else
    #define DBG_NEW new
#endif

struct Pod {
    int x;
};

void main() {
    Pod* pPod = DBG_NEW Pod;
    pPod = DBG_NEW Pod; // Oops, leaked the original pPod!
    delete pPod;

    _CrtDumpMemoryLeaks();
}

Quando você executa esse código no depurador do Visual Studio, a chamada para _CrtDumpMemoryLeaks gera um relatório na janela Saída semelhante a:

Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\debug_new\debug_new.cpp(20) : {75}
 normal block at 0x0098B8C8, 4 bytes long.
 Data: <    > CD CD CD CD
Object dump complete.

Essa saída informa que a alocação vazada estava na linha 20 de debug_new.cpp.

Observação

Não recomendamos que você crie uma macro de pré-processador chamada new ou qualquer outra palavra-chave de linguagem.

Definir pontos de interrupção em um Número de Alocação de Memória

O número de alocação de memória informa quando um bloco de memória vazado tiver sido atribuído. Um bloco com uma alocação de memória de 18, por exemplo, é o 18º bloco de memória alocado durante a execução do aplicativo. O relatório crt conta todas as alocações de bloco de memória durante a execução, incluindo alocações pela biblioteca CRT e outras bibliotecas, como MFC. Portanto, o bloco de alocação de memória número 18 provavelmente não é 18º bloco de memória atribuído pelo seu código.

Você pode usar o número de alocação para definir um ponto de interrupção na alocação da memória.

Para definir um ponto de interrupção de alocação de memória usando a janela Inspeção

  1. Definir um ponto de interrupção perto do início do aplicativo e inicie a depuração.

  2. Quando o aplicativo pausar no ponto de interrupção, abra uma janela Inspeção selecionando Depurar>Windows>Inspeção 1 (ou Inspeção 2, Inspeção 3 ou Inspeção 4).

  3. Na janela Inspeção, digite _crtBreakAlloc na coluna Nome.

    Se estiver usando a versão com multithread da DLL da biblioteca CRT (a opção /MD), adicione o operador de contexto: {,,ucrtbased.dll}_crtBreakAlloc

    Verifique se os símbolos de depuração estão carregados. Caso contrário, _crtBreakAlloc é relatado como não identificado.

  4. Pressione Enter.

    O depurador avalia a chamada e coloca o resultado na coluna de Valor. Esse valor será -1 se você não tiver definido nenhum ponto de interrupção nas alocações de memória.

  5. Na coluna Valor, substitua o valor mostrado com o número de alocação da alocação de memória onde você deseja que o depurador interrompa.

Após definir um ponto de interrupção em um número de alocação de memória, continue a depuração. Execute sob as mesmas condições para que o número de alocação de memória não seja alterado. Quando o programa interrompe a alocação de memória especificada, use a janela Pilha de chamadas e outras janelas de depuração para determinar as condições em que a memória foi atribuída. Em seguida, você pode continuar a execução para observar o que acontece ao objeto e determinar o motivo dele não ser deslocado corretamente.

Definindo um ponto de interrupção de dados no objeto também pode ser útil. Para obter mais informações, confira Pontos de interrupção.

Você também pode definir pontos de interrupção de alocação de memória no código. É possível definir:

_crtBreakAlloc = 18;

ou:

_CrtSetBreakAlloc(18);

Comparar estados de memória

Outra técnica para localizar vazamentos de memória envolve pegar instantâneos do estado da memória do aplicativo em pontos-chave. Para obter um instantâneo do estado da memória em um determinado ponto em seu aplicativo, crie uma estrutura _CrtMemState e passe-a para a função _CrtMemCheckpoint.

_CrtMemState s1;
_CrtMemCheckpoint( &s1 );

A função _CrtMemCheckpoint preenche a estrutura com uma forma instantânea do estado de memória atual.

Para exibir o conteúdo de uma estrutura de _CrtMemState, passe a estrutura para a função _ CrtMemDumpStatistics:

_CrtMemDumpStatistics( &s1 );

_CrtMemDumpStatistics resulta num despejo do estado de memória pa:recido com esse

0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
3071 bytes in 16 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 3071 bytes.
Total allocations: 3764 bytes.

Para determinar se um vazamento de memória ocorreu em uma seção de código, você pode capturar instantâneos do estado da memória antes e após a seção e, em seguida, usar _CrtMemDifference para comparar os dois estados:

_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );

if ( _CrtMemDifference( &s3, &s1, &s2) )
   _CrtMemDumpStatistics( &s3 );

_CrtMemDifference compara os estados de memória s1 e s2 e retorna um resultado em (s3) que é a diferença entre s1 e s2.

Uma técnica para localizar vazamentos de memória começa colocando chamadas de _CrtMemCheckpoint no início e fim do seu aplicativo, depois usando _CrtMemDifference para comparar os resultados. Se _CrtMemDifference mostra um vazamento de memória, você pode adicionar mais chamadas de _CrtMemCheckpoint para dividir seu programa usando uma busca binária até ter isolado a fonte do vazamento.

Falsos positivos

_CrtDumpMemoryLeaks pode fornecer falsas indicações de vazamentos de memória se uma biblioteca marcar alocações internas como blocos normais em vez de blocos CRT ou blocos de cliente. Nesse caso, _CrtDumpMemoryLeaks não é capaz de reconhecer a diferença entre alocações de usuário e alocações internas de biblioteca. Se os destruidores globais das alocações de biblioteca forem executados após o ponto onde você chama _CrtDumpMemoryLeaks, cada alocação interna da biblioteca será relatada como um vazamento de memória. Versões da Biblioteca de Modelos Padrão anteriores ao Visual Studio .NET podem fazer coim que _CrtDumpMemoryLeaks relate esse tipo de falso positivo.

Confira também