Partilhar via


Detalhes do heap de depuração CRT

O heap de depuração CRT e as funções relacionadas fornecem muitas maneiras de rastrear e depurar problemas de gerenciamento de memória em seu código. Você pode usá-lo para localizar saturações de buffer e para controlar e relatar alocações de memória e estado de memória. Ele também tem suporte para criar suas próprias funções de alocação de depuração para suas necessidades exclusivas de aplicativo.

Localizar saturações de buffer com heap de depuração

Dois dos problemas mais comuns e intratáveis que os programadores encontram são substituir o fim de um buffer alocado e vazamentos de memória (falha em liberar alocações depois que elas não são mais necessárias). A pilha de depuração fornece ferramentas poderosas para resolver problemas de alocação de memória desse tipo.

As versões de depuração das funções de heap chamam as versões padrão ou base usadas nas compilações de versão. Quando você solicita um bloco de memória, o gerenciador de heap de depuração aloca do heap base um bloco de memória um pouco maior do que você solicitou e retorna um ponteiro para sua parte desse bloco. Por exemplo, suponha que seu aplicativo contenha a chamada: malloc( 10 ). Em uma compilação de versão, malloc chamaria a rotina de alocação de heap base solicitando uma alocação de 10 bytes. Em uma compilação de depuração, no entanto, malloc chamaria _malloc_dbg, que chamaria a rotina de alocação de heap base solicitando uma alocação de 10 bytes mais aproximadamente 36 bytes de memória extra. Todos os blocos de memória resultantes na pilha de depuração são conectados em uma única lista vinculada, ordenada de acordo com quando foram alocados.

A memória extra alocada pelas rotinas de heap de depuração é usada para informações de contabilidade. Ele tem ponteiros que vinculam blocos de memória de depuração juntos e pequenos buffers em ambos os lados dos dados para capturar substituições da região alocada.

Atualmente, a estrutura de cabeçalho de bloco usada para armazenar as informações de contabilidade do heap de depuração é declarada <crtdbg.h> no cabeçalho e definida no <debug_heap.cpp> arquivo de origem CRT. Conceitualmente, é semelhante a esta estrutura:

typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
    _CrtMemBlockHeader* _block_header_next;
// Pointer to the block allocated just after this one:
    _CrtMemBlockHeader* _block_header_prev;
    char const*         _file_name;
    int                 _line_number;

    int                 _block_use;      // Type of block
    size_t              _data_size;      // Size of user block

    long                _request_number; // Allocation number
// Buffer just before (lower than) the user's memory:
    unsigned char       _gap[no_mans_land_size];

    // Followed by:
    // unsigned char    _data[_data_size];
    // unsigned char    _another_gap[no_mans_land_size];
} _CrtMemBlockHeader;

Os no_mans_land buffers em ambos os lados da área de dados do usuário do bloco têm atualmente 4 bytes de tamanho e são preenchidos com um valor de byte conhecido usado pelas rotinas de heap de depuração para verificar se os limites do bloco de memória do usuário não foram substituídos. A pilha de depuração também preenche novos blocos de memória com um valor conhecido. Se você optar por manter os blocos liberados na lista vinculada da pilha, esses blocos liberados também serão preenchidos com um valor conhecido. Atualmente, os valores reais de bytes usados são os seguintes:

Código Descrição
no_mans_land (0xFD) Os buffers "no_mans_land" em ambos os lados da memória usada por um aplicativo estão atualmente preenchidos com 0xFD.
Blocos liberados (0xDD) Os blocos liberados mantidos sem uso na lista vinculada do heap de depuração quando o _CRTDBG_DELAY_FREE_MEM_DF sinalizador é definido estão atualmente preenchidos com 0xDD.
Novos objetos (0xCD) Novos objetos são preenchidos com 0xCD quando são alocados.

Tipos de blocos na pilha de depuração

Cada bloco de memória na pilha de depuração é atribuído a um dos cinco tipos de alocação. Esses tipos são rastreados e relatados de forma diferente para fins de deteção de vazamentos e relatórios estaduais. Você pode especificar o tipo de um bloco alocando-o usando uma chamada direta para uma das funções de alocação de heap de depuração, como _malloc_dbg. Os cinco tipos de blocos de memória no heap de depuração (definido no nBlockUse membro da estrutura) são os _CrtMemBlockHeader seguintes:

_NORMAL_BLOCK
Uma chamada para malloc ou calloc cria um bloco Normal. Se você pretende usar apenas blocos Normal e não tem necessidade de blocos de Cliente, convém definir _CRTDBG_MAP_ALLOC. _CRTDBG_MAP_ALLOC faz com que todas as chamadas de alocação de heap sejam mapeadas para seus equivalentes de depuração em compilações de depuração. Ele permite o armazenamento de informações de nome de arquivo e número de linha sobre cada chamada de alocação no cabeçalho do bloco correspondente.

_CRT_BLOCK
Os blocos de memória alocados internamente por muitas funções de biblioteca de tempo de execução são marcados como blocos CRT para que possam ser manipulados separadamente. Como resultado, a deteção de vazamentos e outras operações podem permanecer inalteradas por eles. Uma alocação nunca deve alocar, realocar ou liberar qualquer bloco do tipo CRT.

_CLIENT_BLOCK
Um aplicativo pode manter um controle especial de um determinado grupo de alocações para fins de depuração, alocando-as como esse tipo de bloco de memória, usando chamadas explícitas para as funções de heap de depuração. MFC, por exemplo, aloca todos os CObject objetos como blocos Cliente, outros aplicativos podem manter objetos de memória diferentes em blocos Cliente. Subtipos de blocos de cliente também podem ser especificados para maior granularidade de rastreamento. Para especificar subtipos de blocos de cliente, desloque o número para a esquerda por 16 bits e OR ele com _CLIENT_BLOCK. Por exemplo:

#define MYSUBTYPE 4
freedbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16));

Uma função de gancho fornecida pelo cliente para despejar os objetos armazenados nos blocos do cliente pode ser instalada usando _CrtSetDumpCliento , e será chamada sempre que um bloco do cliente for despejado por uma função de depuração. Além disso, _CrtDoForAllClientObjects pode ser usado para chamar uma determinada função fornecida pelo aplicativo para cada bloco de cliente no heap de depuração.

_FREE_BLOCK
Normalmente, os blocos que são liberados são removidos da lista. Para verificar se a memória liberada não está gravada ou para simular condições de pouca memória, você pode manter os blocos liberados na lista vinculada, marcados como Livre e preenchidos com um valor de byte conhecido (atualmente 0xDD).

_IGNORE_BLOCK
É possível desativar as operações de heap de depuração por algum intervalo. Durante esse tempo, os blocos de memória são mantidos na lista, mas são marcados como blocos de ignorar.

Para determinar o tipo e subtipo de um determinado bloco, use a função _CrtReportBlockType e as macros _BLOCK_TYPE e _BLOCK_SUBTYPE. As macros são definidas da seguinte forma <crtdbg.h> :

#define _BLOCK_TYPE(block)          (block & 0xFFFF)
#define _BLOCK_SUBTYPE(block)       (block >> 16 & 0xFFFF)

Verifique se há integridade da pilha e vazamentos de memória

Muitos dos recursos do heap de depuração devem ser acessados de dentro do seu código. A seção a seguir descreve alguns dos recursos e como usá-los.

_CrtCheckMemory
Você pode usar uma chamada para _CrtCheckMemory, por exemplo, para verificar a integridade da pilha a qualquer momento. Esta função inspeciona todos os blocos de memória na pilha. Ele verifica se as informações do cabeçalho do bloco de memória são válidas e confirma que os buffers não foram modificados.

_CrtSetDbgFlag
Você pode controlar como o heap de depuração controla as alocações usando um sinalizador interno, _crtDbgFlagque pode ser lido e definido usando a _CrtSetDbgFlag função. Ao alterar esse sinalizador, você pode instruir a pilha de depuração a verificar se há vazamentos de memória quando o programa for encerrado e relatar quaisquer vazamentos detetados. Da mesma forma, você pode dizer à pilha para deixar blocos de memória liberados na lista vinculada, para simular situações de pouca memória. Quando a pilha é verificada, esses blocos liberados são inspecionados em sua totalidade para garantir que não tenham sido perturbados.

O _crtDbgFlag sinalizador contém os seguintes campos de bits:

Campo de bits Valor predefinido Descrição
_CRTDBG_ALLOC_MEM_DF Ligado Ativa a alocação de depuração. Quando esse bit está desativado, as alocações permanecem encadeadas, mas seu tipo de bloco é _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF Desativado Impede que a memória seja realmente liberada, como para simular condições de pouca memória. Quando esse bit está ativado, os blocos liberados são mantidos na lista vinculada do heap de depuração, mas são marcados como _FREE_BLOCK e preenchidos com um valor de byte especial.
_CRTDBG_CHECK_ALWAYS_DF Desativado Causas _CrtCheckMemory a serem chamadas em cada alocação e deallocation. A execução é mais lenta, mas deteta erros rapidamente.
_CRTDBG_CHECK_CRT_DF Desativado Faz com que blocos marcados como tipo _CRT_BLOCK sejam incluídos em operações de deteção de vazamentos e diferença de estado. Quando esse bit está desligado, a memória usada internamente pela biblioteca de tempo de execução é ignorada durante essas operações.
_CRTDBG_LEAK_CHECK_DF Desativado Faz com que a verificação de vazamento seja executada na saída do programa por meio de uma chamada para _CrtDumpMemoryLeaks. Um relatório de erros é gerado se o aplicativo não conseguiu liberar toda a memória que alocou.

Configurar a pilha de depuração

Todas as chamadas para funções de heap como malloc, free, calloc, realloc, newe delete resolvem para depurar versões dessas funções que operam no heap de depuração. Quando você libera um bloco de memória, a pilha de depuração verifica automaticamente a integridade dos buffers em ambos os lados da área alocada e emite um relatório de erros se a substituição tiver ocorrido.

Para usar a pilha de depuração

Vincule a compilação de depuração do seu aplicativo com uma versão de depuração da biblioteca de tempo de execução C.

Para alterar um ou mais _crtDbgFlag campos de bits e criar um novo estado para o sinalizador

  1. Chame _CrtSetDbgFlag com o newFlag parâmetro definido como _CRTDBG_REPORT_FLAG (para obter o estado atual _crtDbgFlag ) e armazene o valor retornado em uma variável temporária.

  2. Ative todos os bits usando um operador bit a | bit ("ou") na variável temporária com as máscaras de bits correspondentes (representadas no código do aplicativo por constantes de manifesto).

  3. Desligue os outros bits usando um operador bit a & bit ("e") na variável com um operador bit a ~ bit ("não" ou complemento) das máscaras de bits apropriadas.

  4. Chame _CrtSetDbgFlag com o newFlag parâmetro definido para o valor armazenado na variável temporária para criar o novo estado para _crtDbgFlag.

    Por exemplo, as seguintes linhas de código permitem a deteção automática de fugas e desativam as verificações de blocos do tipo _CRT_BLOCK:

    // Get current flag
    int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
    
    // Turn on leak-checking bit.
    tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
    
    // Turn off CRT block checking bit.
    tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;
    
    // Set flag to the new value.
    _CrtSetDbgFlag( tmpFlag );
    

new, deletee _CLIENT_BLOCK alocações no heap de depuração C++

As versões de depuração da biblioteca de tempo de execução C contêm versões de depuração do C++ new e delete operadores. Se você usar o _CLIENT_BLOCK tipo de alocação, deverá chamar a versão de depuração do new operador diretamente ou criar macros que substituam o new operador no modo de depuração, conforme mostrado no exemplo a seguir:

/* MyDbgNew.h
 Defines global operator new to allocate from
 client blocks
*/

#ifdef _DEBUG
   #define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
   #define DEBUG_CLIENTBLOCK
#endif // _DEBUG

/* MyApp.cpp
        Use a default workspace for a Console Application to
 *      build a Debug version of this code
*/

#include "crtdbg.h"
#include "mydbgnew.h"

#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif

int main( )   {
    char *p1;
    p1 =  new char[40];
    _CrtMemDumpAllObjectsSince( NULL );
}

A versão de depuração do delete operador funciona com todos os tipos de bloco e não requer alterações no seu programa quando você compila uma versão de lançamento.

Funções de relatório de estado de pilha

Para capturar um instantâneo resumido do estado da pilha em um determinado momento, use a _CrtMemState estrutura definida em <crtdbg.h>:

typedef struct _CrtMemState
{
    // Pointer to the most recently allocated block:
    struct _CrtMemBlockHeader * pBlockHeader;
    // A counter for each of the 5 types of block:
    size_t lCounts[_MAX_BLOCKS];
    // Total bytes allocated in each block type:
    size_t lSizes[_MAX_BLOCKS];
    // The most bytes allocated at a time up to now:
    size_t lHighWaterCount;
    // The total bytes allocated at present:
    size_t lTotalCount;
} _CrtMemState;

Essa estrutura salva um ponteiro para o primeiro bloco (alocado mais recentemente) na lista vinculada do heap de depuração. Em seguida, em duas matrizes, ele registra quantos de cada tipo de bloco de memória (_NORMAL_BLOCK, _CLIENT_BLOCK, _FREE_BLOCK, e assim por diante) estão na lista e o número de bytes alocados em cada tipo de bloco. Finalmente, ele registra o maior número de bytes alocados na pilha como um todo até esse ponto, e o número de bytes atualmente alocados.

Outras funções de relatório CRT

As funções a seguir relatam o estado e o conteúdo da pilha e usam as informações para ajudar a detetar vazamentos de memória e outros problemas.

Função Descrição
_CrtMemCheckpoint Salva um instantâneo da pilha em uma _CrtMemState estrutura fornecida pelo aplicativo.
_CrtMemDifference Compara duas estruturas de estado de memória, salva a diferença entre elas em uma estrutura de terceiro estado e retorna TRUE se os dois estados forem diferentes.
_CrtMemDumpStatistics Despeja uma determinada _CrtMemState estrutura. A estrutura pode conter um instantâneo do estado da pilha de depuração em um determinado momento ou a diferença entre dois instantâneos.
_CrtMemDumpAllObjectsSince Despeja informações sobre todos os objetos alocados desde que um determinado instantâneo foi tirado da pilha ou desde o início da execução. Toda vez que ele despeja um _CLIENT_BLOCK bloco, ele chama uma função de gancho fornecida pelo aplicativo, se um tiver sido instalado usando _CrtSetDumpClient.
_CrtDumpMemoryLeaks Determina se ocorreram vazamentos de memória desde o início da execução do programa e, em caso afirmativo, despeja todos os objetos alocados. Toda vez _CrtDumpMemoryLeaks que despeja um _CLIENT_BLOCK bloco, ele chama uma função de gancho fornecida pelo aplicativo, se uma tiver sido instalada usando _CrtSetDumpCliento .

Rastrear solicitações de alocação de heap

Saber o nome do arquivo de origem e o número da linha de uma macro de declaração ou relatório geralmente é útil para localizar a causa de um problema. O mesmo não é tão provável de ser verdade para as funções de alocação de heap. Embora você possa inserir macros em muitos pontos apropriados na árvore lógica de um aplicativo, uma alocação geralmente é enterrada em uma função que é chamada de muitos lugares diferentes em muitos momentos diferentes. A questão não é qual linha de código fez uma má alocação. Em vez disso, é qual das milhares de alocações feitas por essa linha de código foi ruim e por quê.

Números únicos de pedidos de atribuição e _crtBreakAlloc

Há uma maneira simples de identificar a chamada de alocação de heap específica que deu errado. Ele aproveita o número de solicitação de alocação exclusivo associado a cada bloco no heap de depuração. Quando as informações sobre um bloco são relatadas por uma das funções de dump, esse número de solicitação de alocação é incluído em chaves. Por exemplo, {36}.

Depois de saber o número da solicitação de alocação de um bloco alocado incorretamente, você pode passar esse número para _CrtSetBreakAlloc criar um ponto de interrupção. A execução será interrompida pouco antes de alocar o bloco e você pode voltar atrás para determinar qual rotina foi responsável pela chamada incorreta. Para evitar a recompilação, você pode fazer a mesma coisa no depurador definindo _crtBreakAlloc o número da solicitação de alocação em que está interessado.

Criando versões de depuração de suas rotinas de alocação

Uma abordagem mais complexa é criar versões de depuração de suas próprias rotinas de alocação, comparáveis às _dbg versões das funções de alocação de heap. Em seguida, você pode passar os argumentos do arquivo de origem e do número de linha para as rotinas de alocação de heap subjacentes e poderá ver imediatamente onde uma alocação incorreta se originou.

Por exemplo, suponha que seu aplicativo contenha uma rotina comumente usada semelhante ao exemplo a seguir:

int addNewRecord(struct RecStruct * prevRecord,
                 int recType, int recAccess)
{
    // ...code omitted through actual allocation...
    if ((newRec = malloc(recSize)) == NULL)
    // ... rest of routine omitted too ...
}

Em um arquivo de cabeçalho, você pode adicionar código como o exemplo a seguir:

#ifdef _DEBUG
#define  addNewRecord(p, t, a) \
            addNewRecord(p, t, a, __FILE__, __LINE__)
#endif

Em seguida, você pode alterar a alocação em sua rotina de criação de registros da seguinte maneira:

int addNewRecord(struct RecStruct *prevRecord,
                int recType, int recAccess
#ifdef _DEBUG
               , const char *srcFile, int srcLine
#endif
    )
{
    /* ... code omitted through actual allocation ... */
    if ((newRec = _malloc_dbg(recSize, _NORMAL_BLOCK,
            srcFile, scrLine)) == NULL)
    /* ... rest of routine omitted too ... */
}

Agora, o nome do arquivo de origem e o número da linha onde addNewRecord foi chamado serão armazenados em cada bloco resultante alocado no heap de depuração e serão relatados quando esse bloco for examinado.

Ver também

Depurando código nativo