Detalhes de heap de depuração do 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 estouros 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 (não liberar alocações depois que elas não são mais necessárias). O heap de depuração fornece ferramentas avançadas para resolver problemas de alocação de memória desse tipo.

As versões de depuração de funções heap chamam o padrão ou as versões de base usadas nas compilações da release. 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 o solicitado e retorna um ponteiro para sua parte desse bloco. Por exemplo, suponha que seu aplicativo contém a chamada: malloc( 10 ). Em uma compilação de Release, 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 no heap de depuração estão conectados em uma única lista vinculada, ordenados de acordo com a data em que 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 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 escrituração do heap de depuração é declarada <crtdbg.h> no cabeçalho e definida no <debug_heap.cpp> arquivo de origem CRT. Conceitualmente, é semelhante a essa 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. O heap 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 do heap, esses blocos liberados também serão preenchidos com um valor conhecido. Atualmente, os valores reais de bytes são usados como segue:

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 não usados na lista vinculada da heap de depuração quando o sinalizador de _CRTDBG_DELAY_FREE_MEM_DF for ajustado serão preenchidos com 0xDD atualmente.

Novos objetos (0xCD)
Novos objetos são preenchidos com 0xCD quando são alocados.

Tipos de blocos na heap de depuração

Cada bloco de memória no heap de depuração é atribuído a um dos cinco tipos de alocação. Esses tipos são controlados e relatados de maneira diferente para fins de relatórios de estado e de detecção de vazamento. 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 (definidos 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 Normais e não precisa 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 de bloco correspondente.

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

_CLIENT_BLOCK
Um aplicativo pode manter um acompanhamento 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 funções de heap de depuração. O MFC, por exemplo, aloca todos os CObject objetos como blocos de cliente, outros aplicativos podem manter objetos de memória diferentes em blocos de cliente. Os 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 à esquerda por 16 bits e OR 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 em blocos de cliente pode ser instalada usando _CrtSetDumpCliento , e será chamada sempre que um bloco de 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 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 Livres 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 este momento, blocos de memória são mantidos na lista, mas marcados como blocos Ignorar.

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

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

{1>Verifique a integridade e vazamentos de memória do heap<1}

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

_CrtCheckMemory
Você pode usar uma chamada para, por exemplo, para _CrtCheckMemoryverificar a integridade do heap a qualquer momento. Essa função inspeciona cada bloco de memória no heap. Ele verifica se as informações de 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, _crtDbgFlag, que pode ser lido e definido usando a _CrtSetDbgFlag função. Alterando este sinalizador, você pode instruir o heap de depuração para verificar vazamentos de memória quando o programa encerra e relata todos os vazamentos detectados. Da mesma forma, você pode dizer ao heap para deixar blocos de memória liberados na lista vinculada, para simular situações de pouca memória. Quando o heap é verificado, 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 padrão Descrição
_CRTDBG_ALLOC_MEM_DF Ativado 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 liberada realmente para simular condições de memória baixa. 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 a cada alocação e deallocation. A execução é mais lenta, mas detecta erros rapidamente.
_CRTDBG_CHECK_CRT_DF Desativado Faz com que os blocos marcados como tipo _CRT_BLOCK sejam incluídos em operações de detecção de vazamento e diferença de estado. Quando esse bit está desativado, a memória usada internamente pela biblioteca em 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 erro é gerado se o aplicativo não liberou qualquer memória atribuída.

Configurar o heap de depuração

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

Para usar a heap 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 bit e criar um novo estado para o sinalizador

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

  2. Ative quaisquer 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. Desative 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. Chamar _CrtSetDbgFlag com o parâmetro de newFlag definido como o valor armazenado na variável temporária para criar o novo estado para _crtDbgFlag.

    Por exemplo, as seguintes linhas de código habilitam a detecção automática de vazamentos e desabilitam 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 do C++

As versões de depuração de biblioteca em tempo de execução de C contêm versões de depuração do C++ new e operadores de delete. Se você usar o tipo de alocação _CLIENT_BLOCK, deverá chamar a versão de depuração do operador new diretamente ou criar macros que substituam o operador new no modo de depuração, como 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 operador delete funciona com todos os tipos de bloco e não requer nenhuma alteração em seu programa quando você compilar uma versão de lançamento.

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

Para capturar um instantâneo resumido do estado do heap 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 (recentemente atribuído) na lista vinculada da 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_BLOCKe assim por diante) estão na lista e o número de bytes alocados em cada tipo de bloco. Finalmente, registra o maior número de bytes atribuídos no heap como um todo até esse ponto, e o número de bytes atribuídos no momento.

Outras funções de relatório CRT

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

Função Descrição
_CrtMemCheckpoint Salva um instantâneo do heap 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 estado e retorna VERDADEIRO se os dois estados forem diferentes.
_CrtMemDumpStatistics Despeja uma determinada _CrtMemState estrutura. A estrutura pode conter um instantâneo de estado da heap de depuração em um determinado momento ou a diferença entre os dois instantâneos.
_CrtMemDumpAllObjectsSince Despeja informações sobre todos os objetos atribuídos como um instantâneo determinado extraído do heap do início de execução. Toda vez que ele despeja um bloco, ele chama uma função de gancho fornecida pelo aplicativo, se um _CLIENT_BLOCK tiver sido instalado usando _CrtSetDumpClient.
_CrtDumpMemoryLeaks Determina se qualquer vazamento de memória ocorreu desde o início da execução do programa e, em caso afirmativo, despeja todos os objetos atribuídos. Toda vez que _CrtDumpMemoryLeaks despeja um bloco, ele chama uma função de gancho fornecida pelo aplicativo, se um _CLIENT_BLOCK tiver sido instalado usando _CrtSetDumpCliento .

Controlar 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 verdadeiro para 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 alocação ruim. Em vez disso, é qual das milhares de alocações feitas por essa linha de código foi ruim e por quê.

Números de solicitação de alocação exclusivos 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 despejo, esse número de solicitação de alocação é colocado entre chaves (por exemplo, "{36}").

Depois de saber o número de 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 logo após a alocação do bloco, e é possível voltar de modo a determinar que 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 de 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 registro como a seguir:

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 atribuído no heap de depuração e relatados quando esse bloco é examinado.

Confira também

Depurando código nativo