Informazioni dettagliate sull'heap di debug CRT

L'heap di debug CRT e le funzioni correlate offrono molti modi per tenere traccia dei problemi di gestione della memoria ed eseguirne il debug nel codice. È possibile usarlo per trovare sovraccarichi del buffer e per tenere traccia e segnalare le allocazioni di memoria e lo stato della memoria. Include anche il supporto per la creazione di funzioni di allocazione di debug personalizzate per le esigenze specifiche dell'app.

Individuare i sovraccarichi del buffer con l'heap di debug

Due dei problemi più comuni e intrattibili riscontrati dai programmatori stanno sovrascrivendo la fine di un buffer allocato e delle perdite di memoria (non riesce a liberare le allocazioni dopo che non sono più necessarie). L'heap di debug offre strumenti estremamente efficaci per la risoluzione dei problemi di allocazione di memoria di questo tipo.

Le versioni di debug delle funzioni degli heap chiamano le versioni standard o di base utilizzate nelle build di rilascio. Quando si richiede un blocco di memoria, il gestore dell'heap di debug alloca dall'heap di base un blocco di memoria leggermente più grande di quello richiesto e restituisce un puntatore alla parte del blocco. Si supponga ad esempio che l'applicazione contenga la chiamata: malloc( 10 ). In una build malloc di rilascio chiamare la routine di allocazione dell'heap di base che richiede un'allocazione di 10 byte. In una compilazione di debug, tuttavia, malloc chiamerebbe _malloc_dbg, che chiamerebbe quindi la routine di allocazione dell'heap di base richiedendo un'allocazione di 10 byte più circa 36 byte di memoria aggiuntiva. Tutti i blocchi di memoria generati nell'heap di debug sono connessi in un unico elenco collegato, ordinato in base al momento dell'allocazione.

La memoria aggiuntiva allocata dalle routine dell'heap di debug viene usata per la contabilità delle informazioni. Include puntatori che collegano i blocchi di memoria di debug insieme e buffer di piccole dimensioni su entrambi i lati dei dati per intercettare le sovrascrizioni dell'area allocata.

Attualmente, la struttura dell'intestazione di blocco usata per archiviare le informazioni di conservazione dell'heap di debug viene dichiarata nell'intestazione <crtdbg.h> e definita nel <debug_heap.cpp> file di origine CRT. Concettualmente, è simile a questa struttura:

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;

I no_mans_land buffer su entrambi i lati dell'area dati utente del blocco sono attualmente di 4 byte e vengono riempiti con un valore byte noto usato dalle routine dell'heap di debug per verificare che i limiti del blocco di memoria dell'utente non siano stati sovrascritti. L'heap di debug riempie inoltre i nuovi blocchi di memoria con un valore noto. Se si sceglie di mantenere i blocchi liberati nell'elenco collegato dell'heap, questi blocchi liberati vengono riempiti anche con un valore noto. Attualmente i valori byte utilizzati sono i seguenti:

no_mans_land (0xFD)
I buffer "no_mans_land" su entrambi i lati della memoria usata da un'applicazione sono attualmente riempiti con 0xFD.

Blocchi liberati (0xDD)
I blocchi liberati mantenuti inutilizzati nell'elenco collegato dell'heap di debug quando è impostato il flag _CRTDBG_DELAY_FREE_MEM_DF sono attualmente riempiti con il valore 0xDD.

Nuovi oggetti (0xCD)
I nuovi oggetti vengono riempiti con 0xCD quando vengono allocati.

Tipi di blocchi sull'heap di debug

Ciascun blocco di memoria dell'heap di debug viene assegnato a uno di cinque tipi di allocazione. Questi tipi vengono registrati e visualizzati nei report in modo diverso per il rilevamento di perdite e i report sullo stato. È possibile specificare il tipo di un blocco allocandolo usando una chiamata diretta a una delle funzioni di allocazione dell'heap di debug, _malloc_dbgad esempio . I cinque tipi di blocchi di memoria nell'heap di debug (impostati nel nBlockUse membro della _CrtMemBlockHeader struttura) sono i seguenti:

_NORMAL_BLOCK
Una chiamata a malloc o calloc crea un blocco Normal. Se si intende usare solo blocchi Normali e non sono necessari blocchi client, è possibile definire _CRTDBG_MAP_ALLOC. _CRTDBG_MAP_ALLOC fa in modo che tutte le chiamate di allocazione dell'heap vengano mappate agli equivalenti di debug nelle compilazioni di debug. Consente l'archiviazione di informazioni sul nome file e sul numero di riga relative a ogni chiamata di allocazione nell'intestazione del blocco corrispondente.

_CRT_BLOCK
I blocchi di memoria allocati internamente da numerose funzioni della libreria di runtime sono contrassegnati come blocchi CRT, in modo da poter essere gestiti separatamente. Di conseguenza, il rilevamento delle perdite e altre operazioni potrebbero non essere interessate da tali operazioni. Le allocazioni non devono mai allocare, riallocare o liberare blocchi di tipo CRT.

_CLIENT_BLOCK
Un'applicazione può tenere traccia con modalità speciali di un dato gruppo di allocazioni a scopo di debug effettuando tali allocazioni con questo tipo di blocco di memoria, utilizzando chiamate esplicite alle funzioni dello heap di debug. MFC, ad esempio, alloca tutti gli CObject oggetti come blocchi Client. Altre applicazioni potrebbero mantenere oggetti di memoria diversi nei blocchi client. È inoltre possibile specificare sottotipi dei blocchi client per consentire una registrazione più differenziata. Per specificare sottotipi di blocchi client, spostare il numero verso sinistra di 16 bit ed effettuare un'operazione OR su di esso con _CLIENT_BLOCK. Ad esempio:

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

Una funzione hook fornita dal client per il dump degli oggetti archiviati nei blocchi client può essere installata tramite _CrtSetDumpCliente verrà quindi chiamata ogni volta che un blocco Client viene sottoposto a dump da una funzione di debug. _CrtDoForAllClientObjects Può anche essere usato per chiamare una determinata funzione fornita dall'applicazione per ogni blocco client nell'heap di debug.

_FREE_BLOCK
Normalmente i blocchi liberati vengono rimossi dall'elenco. Per verificare che la memoria liberata non sia scritta in o per simulare condizioni di memoria insufficiente, è possibile mantenere i blocchi liberati nell'elenco collegato, contrassegnati come Gratuito e riempiti con un valore di byte noto (attualmente 0xDD).

_IGNORE_BLOCK
È possibile disattivare le operazioni dell'heap di debug per un determinato intervallo. Durante questo periodo i blocchi di memoria vengono mantenuti nell'elenco, ma vengono contrassegnati come blocchi da ignorare.

Per determinare il tipo e il sottotipo di un determinato blocco, utilizzare la funzione _CrtReportBlockType e le _BLOCK_TYPE macro e _BLOCK_SUBTYPE. Le macro sono definite nel <crtdbg.h> modo seguente:

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

Verificare l'integrità dell'heap e le perdite di memoria

È necessario accedere a diverse funzionalità dell'heap di debug dall'interno del codice. Nella seguente sezione vengono descritte alcune funzionalità e le relative modalità di utilizzo.

_CrtCheckMemory
È possibile usare una chiamata a _CrtCheckMemory, ad esempio, per controllare l'integrità dell'heap in qualsiasi momento. Questa funzione controlla ogni blocco di memoria nell'heap. Verifica che le informazioni sull'intestazione del blocco di memoria siano valide e conferma che i buffer non sono stati modificati.

_CrtSetDbgFlag
È possibile controllare il modo in cui l'heap di debug tiene traccia delle allocazioni usando un flag interno, _crtDbgFlag, che può essere letto e impostato usando la _CrtSetDbgFlag funzione . Modificando questo flag, è possibile comunicare all'heap di debug di verificare la presenza di eventuali perdite di memoria al termine del programma e di segnalare le perdite rilevate. Analogamente, è possibile indicare all'heap di lasciare i blocchi di memoria liberati nell'elenco collegato, per simulare situazioni di memoria insufficiente. Quando l'heap viene controllato, questi blocchi liberati vengono ispezionati nel loro insieme per assicurarsi che non siano stati disturbati.

Il _crtDbgFlag flag contiene i campi di bit seguenti:

Campo di bit Default value Descrizione
_CRTDBG_ALLOC_MEM_DF Attivato Attiva l'allocazione di debug. Quando questo bit è disattivato, le allocazioni rimangono concatenate, ma il tipo di blocco è _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF Non abilitato Impedisce l'effettiva liberazione della memoria, ad esempio per simulare condizioni di poca memoria. Quando questo bit è attivo, i blocchi liberati vengono mantenuti nell'elenco collegato dell'heap di debug, ma sono contrassegnati come _FREE_BLOCK e riempiti con un valore di byte speciale.
_CRTDBG_CHECK_ALWAYS_DF Non abilitato _CrtCheckMemory Causa la chiamata a ogni allocazione e deallocazione. L'esecuzione è più lenta, ma rileva rapidamente gli errori.
_CRTDBG_CHECK_CRT_DF Non abilitato Fa sì che i blocchi contrassegnati come tipo _CRT_BLOCK siano inclusi nelle operazioni di rilevamento delle perdite e differenza di stato. Quando questo bit è off, la memoria utilizzata internamente dalla libreria di runtime viene ignorata durante queste operazioni.
_CRTDBG_LEAK_CHECK_DF Non abilitato Causa l'esecuzione del controllo delle perdite all'uscita del programma tramite una chiamata a _CrtDumpMemoryLeaks. Se l'applicazione non ha liberato tutta la memoria allocata, verrà generato un report di errore.

Configurare l'heap di debug

Tutte le chiamate alle funzioni dell'heap, quali malloc, free, calloc, realloc, new e delete vengono risolte nelle versioni di debug di tali funzioni che operano nell'heap di debug. Quando si libera un blocco di memoria, l'heap di debug controlla automaticamente l'integrità dei buffer presenti da entrambi i lati dell'area allocata e genera un messaggio di errore se si è verificata una sovrascrittura.

Per utilizzare l'heap di debug

  • Collegare la build di debug dell'applicazione con una versione di debug della libreria di runtime C.

Per modificare uno o più _crtDbgFlag campi di bit e creare un nuovo stato per il flag

  1. Chiamare _CrtSetDbgFlag con il parametro newFlag impostato su _CRTDBG_REPORT_FLAG (per ottenere lo stato corrente di _crtDbgFlag) e archiviare il valore restituito in una variabile temporanea.

  2. Attivare qualsiasi bit usando un operatore bit per | bit ("o") sulla variabile temporanea con le maschera di bit corrispondenti (rappresentate nel codice dell'applicazione da costanti manifesto).

  3. Disattivare gli altri bit usando un operatore bit per & bit ("e") sulla variabile con un operatore bit per ~ bit ("not" o complemento) delle maschera di bit appropriate.

  4. Chiamare _CrtSetDbgFlag con il parametro newFlag impostato sul valore archiviato nella variabile temporanea per creare il nuovo stato di _crtDbgFlag.

    Ad esempio, le righe di codice seguenti abilitano il rilevamento automatico delle perdite e disabilitano i controlli per i blocchi di 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 );
    

newAllocazioni , deletee _CLIENT_BLOCK nell'heap di debug C++

Le versioni di debug della libreria di runtime C contengono le versioni di debug degli operatori C++ new e delete. Se si utilizza il tipo di allocazione _CLIENT_BLOCK, è necessario chiamare la versione di debug dell'operatore new in modo diretto oppure creare macro che sostituiscano l'operatore new nella modalità di debug, come illustrato nell'esempio che segue:

/* 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 );
}

La versione di debug dell'operatore delete può essere utilizzata con tutti i tipi di blocco e non è necessario apportare alcuna modifica al programma quando si compila una versione di rilascio.

Funzioni di creazione di report sullo stato dell'heap

Per acquisire uno snapshot di riepilogo dello stato dell'heap in un determinato momento, usare la _CrtMemState struttura definita in <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;

Questa struttura salva un puntatore al primo blocco (quello allocato più di recente) dell'elenco collegato dell'heap di debug. Quindi, in due matrici, registra il numero di ogni tipo di blocco di memoria (_NORMAL_BLOCK, _CLIENT_BLOCK, _FREE_BLOCKe così via) nell'elenco e il numero di byte allocati in ogni tipo di blocco. Riporta infine il massimo numero di byte allocato come singola entità nello heap fino a tal punto.

Altre funzioni di creazione di report CRT

Le funzioni elencate di seguito indicano lo stato e il contenuto dell'heap e utilizzano tali informazioni per il rilevamento di perdite di memoria e altri problemi.

Funzione Descrizione
_CrtMemCheckpoint Salva uno snapshot dell'heap in una _CrtMemState struttura fornita dall'applicazione.
_CrtMemDifference Confronta due strutture dello stato della memoria, salva le differenze tra di esse in una terza struttura di stato e restituisce TRUE se i due stati sono differenti.
_CrtMemDumpStatistics Esegue il dump di una determinata _CrtMemState struttura. La struttura può contenere uno snapshot dello stato dell'heap di debug in un dato momento o le differenze tra i due snapshot.
_CrtMemDumpAllObjectsSince Esegue il dump di informazioni su tutti gli oggetti allocati dopo la generazione di un dato snapshot dell'heap o dall'inizio dell'esecuzione. Ogni volta che esegue il dump di un _CLIENT_BLOCK blocco, chiama una funzione hook fornita dall'applicazione, se ne è stata installata una usando _CrtSetDumpClient.
_CrtDumpMemoryLeaks Determina se si sono verificate perdite di memoria dall'inizio dell'esecuzione del programma e, in caso positivo, esegue il dump di tutti gli oggetti allocati. Ogni volta che _CrtDumpMemoryLeaks esegue il dump di un _CLIENT_BLOCK blocco, chiama una funzione hook fornita dall'applicazione, se ne è stata installata una usando _CrtSetDumpClient.

Tenere traccia delle richieste di allocazione dell'heap

Conoscere il nome del file di origine e il numero di riga di una macro assert o reporting è spesso utile per individuare la causa di un problema. Lo stesso non è così probabile che sia vero delle funzioni di allocazione dell'heap. Sebbene sia possibile inserire macro in molti punti appropriati nell'albero della logica di un'applicazione, un'allocazione viene spesso nascosta in una funzione chiamata da molte posizioni diverse in momenti diversi. La domanda non è quale riga di codice ha creato un'allocazione errata. È invece che una delle migliaia di allocazioni effettuate da tale riga di codice non è valida e perché.

Numeri di richiesta di allocazione univoci e _crtBreakAlloc

Esiste un modo semplice per identificare la chiamata di allocazione dell'heap specifica non valida. Sfrutta il numero di richiesta di allocazione univoco associato a ogni blocco nell'heap di debug. Quando le informazioni relative a un blocco vengono restituite da una delle funzioni dump, questo numero di richiesta di allocazione è racchiuso tra parentesi, ad esempio "{36}".

Quando si conosce il numero di richiesta di allocazione di un blocco allocato in modo non corretto, è possibile passare questo numero a _CrtSetBreakAlloc per creare un punto di interruzione. L'esecuzione si interromperà appena prima dell'allocazione del blocco e sarà possibile risalire i passi del codice in senso contrario all'esecuzione per determinare la routine responsabile della chiamata errata. Per evitare la ricompilazione, è possibile eseguire la stessa operazione nel debugger impostando _crtBreakAlloc sul numero di richiesta di allocazione a cui si è interessati.

Creazione di versioni di debug delle routine di allocazione

Un approccio più complesso consiste nel creare versioni di debug delle routine di allocazione personalizzate, paragonabili alle _dbg versioni delle funzioni di allocazione dell'heap. È quindi possibile passare gli argomenti del file di origine e del numero di riga alle routine di allocazione dell'heap sottostanti e sarà immediatamente possibile vedere dove è stata originata un'allocazione non valida.

Si supponga, ad esempio, che l'applicazione contenga una routine di uso comune simile all'esempio seguente:

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

In un file di intestazione è possibile aggiungere codice come l'esempio seguente:

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

Si provvederebbe quindi a modificare l'allocazione nella routine di creazione record come segue:

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 ... */
}

Il nome del file di origine e il numero di riga in cui è stata effettuata la chiamata a addNewRecord verranno memorizzati in ciascun blocco allocato nell'heap di debug e verranno indicati quando il blocco verrà esaminato.

Vedi anche

Debug del codice nativo