Partilhar via


Encontre vazamentos de memória com a biblioteca CRT

Os vazamentos de memória estão entre os bugs mais sutis e difíceis de detetar em aplicativos C/C++. Vazamentos de memória resultam da falha em desalocar corretamente a memória que foi alocada anteriormente. Um pequeno vazamento de memória pode não ser notado no início, mas com o tempo pode causar sintomas que variam de baixo desempenho a falhas quando o aplicativo fica sem memória. Um aplicativo com vazamento que usa toda a memória disponível pode fazer com que outros aplicativos falhem, criando confusão sobre qual aplicativo é responsável. Mesmo vazamentos de memória inofensivos podem indicar outros problemas que devem ser corrigidos.

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

Ativar a deteção de fugas de memória

As principais ferramentas para detetar 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 #define instrução mapeia uma versão base das funções de heap CRT para a versão de depuração correspondente. Se você deixar de fora a #define instrução, o despejo de vazamento de memória será menos detalhado.

Incluindo crtdbg.h mapeia as funções e malloc para suas versões de depuração, free e _malloc_dbg, que rastreiam a _free_dbg alocação de memória e deallocation. Esse mapeamento ocorre apenas em compilações de depuração, que têm _DEBUG. Versões de lançamento usam o ordinário malloc e free funções.

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 _CrtDumpMemoryLeaks aplicativo para exibir um relatório de vazamento de memória quando o aplicativo for encerrado.

_CrtDumpMemoryLeaks();

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

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

Por padrão, _CrtDumpMemoryLeaks o relatório de vazamento de memória é enviado para o painel Depurar da janela 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 de volta 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();
}

Interpretar o relatório de fuga de memória

Se o seu aplicativo não definir _CRTDBG_MAP_ALLOCo , _CrtDumpMemoryLeaks exibirá um relatório de vazamento de memória com a seguinte aparência:

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 terá a seguinte aparência:

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 o número da linha onde a memória vazada é alocada pela primeira vez.

Quer você defina _CRTDBG_MAP_ALLOCou não, o relatório de vazamento de memória exibe:

  • O número de alocação de memória, que está 18 no exemplo
  • O tipo de bloco, normal no exemplo.
  • O local da memória hexadecimal, 0x00780E80 no exemplo.
  • O tamanho do bloco, 64 bytes no exemplo.
  • Os primeiros 16 bytes de dados no bloco , em formato hexadecimal.

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

Um bloco CRT é alocado pela biblioteca 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 vazamento de memória, a menos que haja problemas sérios com a biblioteca CRT.

Existem dois outros tipos de blocos de memória que nunca aparecem em relatórios de fuga de memória. Um bloco livre é a memória que foi liberada, portanto, por definição, não é vazada. Um bloco de 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 alocada usando a função CRT malloc padrão. Se o programa aloca memória usando o operador C++ new , no entanto, você só pode ver o nome do arquivo e o número da linha onde operator new as chamadas _malloc_dbg no relatório de vazamento de memória. Para criar um relatório de vazamento 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 new operador usando a DBG_NEW macro 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 chamadas _malloc_dbg para gravar as informações extras. Os relatórios de vazamento de memória mostram o nome do arquivo e o número da linha onde os objetos vazados foram alocados. As compilações de versão ainda usam o padrão new. Aqui está 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;
};

int 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 de saída que se parece com:

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.

Esta saída relata 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 idioma.

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 foi alocado. Um bloco com um número de 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 é o 18º bloco de memória alocado pelo seu código.

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

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

  1. Defina um ponto de interrupção perto do início do seu aplicativo e comece a depuração.

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

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

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

    Certifique-se de que 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 Valor . Esse valor é -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 pelo número de alocação da alocação de memória onde você deseja que o depurador quebre.

Depois de definir um ponto de interrupção em um número de alocação de memória, continue a depurar. Certifique-se de executar nas mesmas condições, para que o número de alocação de memória não seja alterado. Quando o programa quebrar na alocação de memória especificada, use a janela Pilha de chamadas e outras janelas do depurador para determinar as condições sob as quais a memória foi alocada. Em seguida, você pode continuar a execução para observar o que acontece com o objeto e determinar por que ele não está corretamente deslocalizado.

Definir um ponto de interrupção de dados no objeto também pode ser útil. Para obter mais informações, consulte Usando pontos de interrupção.

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

_crtBreakAlloc = 18;

ou:

_CrtSetBreakAlloc(18);

Comparar estados de memória

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

_CrtMemState s1;
_CrtMemCheckpoint( &s1 );

A _CrtMemCheckpoint função preenche a estrutura com um instantâneo do estado atual da memória.

Para produzir o conteúdo de uma _CrtMemState estrutura, passe a estrutura para a _CrtMemDumpStatistics função:

_CrtMemDumpStatistics( &s1 );

_CrtMemDumpStatistics Gera um despejo de estado de memória que se parece com:

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 ocorreu uma fuga de memória numa secção de código, pode tirar instantâneos do estado da memória antes e depois da secção e, em seguida, utilizar _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 s1 de memória e s2 retorna um resultado em (s3) que é a diferença entre s1 e s2.

Uma técnica para encontrar vazamentos de memória começa fazendo _CrtMemCheckpoint chamadas no início e no final do seu aplicativo e, em seguida, usando _CrtMemDifference para comparar os resultados. Se _CrtMemDifference mostrar um vazamento de memória, você pode adicionar mais _CrtMemCheckpoint chamadas para dividir seu programa usando uma pesquisa binária, até ter isolado a fonte do vazamento.

Falsos positivos

_CrtDumpMemoryLeaks pode dar indicações falsas de fugas 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 é incapaz de dizer a diferença entre alocações de usuário e alocações de biblioteca interna. Se os destruidores globais para as alocações de biblioteca forem executados após o ponto em que você chama _CrtDumpMemoryLeaks, cada alocação de biblioteca interna será relatada como um vazamento de memória. Versões da biblioteca de modelos padrão anteriores ao Visual Studio .NET podem fazer com _CrtDumpMemoryLeaks que relatar esses falsos positivos.

Ver também