Detalhes de heap de depuração do CRT
O heap de depuração do 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 rastrear 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 sobrescrever o final 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 um build 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 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 de seus 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 arquivo de origem do <debug_heap.cpp>
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. 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 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 builds 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 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 _CrtSetDumpClient
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 Client 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 foi gravada ou para simular condições de memória baixa, 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 _CrtCheckMemory
, por exemplo, para verificar a integridade do heap a qualquer momento. Essa função inspeciona todos os blocos de memória no heap. Ele verifica se as informações do cabeçalho do bloco de memória são válidas e confirma se 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 encadeada, 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 bit:
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 em cada alocação e desalocação. 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 nas 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 o build de depuração do aplicativo a uma versão de depuração da biblioteca de runtime C.
Para alterar um ou mais _crtDbgFlag
campos de bits e criar um novo estado para o sinalizador
Chamar
_CrtSetDbgFlag
com o parâmetronewFlag
definido como_CRTDBG_REPORT_FLAG
(para obter o estado atual de_crtDbgFlag
) e armazenar o valor retornado em uma variável temporária.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).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.Chamar
_CrtSetDbgFlag
com o parâmetro denewFlag
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
, delete
e _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 de resumo 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_BLOCK
, e 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 _CLIENT_BLOCK bloco, ele chama uma função de gancho fornecida pelo aplicativo, se uma tiver sido instalada 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 _CrtDumpMemoryLeaks que despeja um _CLIENT_BLOCK bloco, ele chama uma função de gancho fornecida pelo aplicativo, se uma tiver sido instalada usando _CrtSetDumpClient . |
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 é provável que seja 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 era 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 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 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 no qual 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 argumentos de arquivo de origem e 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 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.