Sdílet prostřednictvím


Podrobnosti haldy ladění CRT

Haldu ladění CRT a související funkce poskytují mnoho způsobů, jak sledovat a ladit problémy se správou paměti v kódu. Můžete ho použít k vyhledání přetečení vyrovnávací paměti a ke sledování a hlášení přidělení paměti a stavu paměti. Má také podporu pro vytváření vlastních funkcí přidělování ladění pro vaše jedinečné potřeby aplikace.

Vyhledání přetečení vyrovnávací paměti pomocí haldy ladění

Dva z nejběžnějších a nepotřebných problémů, se kterými se programátoři setkávají, přepisují konec přidělené vyrovnávací paměti a nevracení paměti (když už nejsou potřeba volné přidělení). Halda ladění poskytuje výkonné nástroje pro řešení problémů s přidělením paměti tohoto typu.

Ladicí verze funkcí haldy volají standardní nebo základní verze používané v buildech vydaných verzí. Když požadujete blok paměti, správce haldy ladění přiděluje ze základní haldy o něco větší blok paměti, než jste požadovali, a vrátí ukazatel na vaši část tohoto bloku. Předpokládejme například, že vaše aplikace obsahuje volání: malloc( 10 ). V sestavení malloc vydané verze by volala rutinu přidělování základní haldy, která požaduje přidělení 10 bajtů. V sestavení ladění by však volal _malloc_dbg, malloc což by pak volal základní rutinu přidělení haldy požadující přidělení 10 bajtů plus přibližně 36 bajtů extra paměti. Všechny výsledné bloky paměti v haldě ladění jsou připojeny v jednom propojeném seznamu seřazené podle toho, kdy byly přiděleny.

K uchovávání informací o účetnictví se používá dodatečná paměť přidělená rutinami haldy ladění. Obsahuje ukazatele, které propojují bloky paměti ladění dohromady a malé vyrovnávací paměti na obou stranách vašich dat, aby zachytily přepsání přidělené oblasti.

V současné době je struktura hlavičky bloku použitá k uložení informací o uchovávání knih haldy ladění deklarována v <crtdbg.h> hlavičce a definována ve zdrojovém <debug_heap.cpp> souboru CRT. Koncepčně se podobá této struktuře:

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;

Vyrovnávací no_mans_land paměti na obou stranách oblasti dat uživatele bloku jsou aktuálně 4 bajty velikosti a jsou vyplněny známou bajtovou hodnotou používanou rutinami haldy ladění k ověření, že limity bloku paměti uživatele nebyly přepsány. Halda ladění také vyplní nové bloky paměti známou hodnotou. Pokud se rozhodnete ponechat uvolněné bloky v propojeném seznamu haldy, jsou tyto uvolněné bloky také vyplněny známou hodnotou. V současné době se skutečné hodnoty bajtů používají takto:

no_mans_land (0xFD)
Vyrovnávací paměti "no_mans_land" na obou stranách paměti používané aplikací jsou aktuálně vyplněny 0xFD.

Uvolněné bloky (0xDD)
Uvolněné bloky se nepoužívaly v propojeném seznamu haldy ladění, když _CRTDBG_DELAY_FREE_MEM_DF je příznak nastaven, jsou aktuálně vyplněny 0xDD.

Nové objekty (0xCD)
Nové objekty jsou vyplněné 0xCD při jejich přidělení.

Typy bloků v haldě ladění

Každý blok paměti v haldě ladění je přiřazen k jednomu z pěti typů přidělení. Tyto typy jsou sledovány a hlášeny odlišně pro účely detekce úniku a hlášení stavu. Typ bloku můžete zadat jeho přidělením pomocí přímého volání některé z funkcí přidělení haldy ladění, například _malloc_dbg. Pět typů paměťových bloků v haldě ladění (nastavené v nBlockUse členu _CrtMemBlockHeader struktury) jsou následující:

_NORMAL_BLOCK
Volání malloc nebo calloc vytvoření normálního bloku Pokud máte v úmyslu používat pouze normální bloky a nemáte k dispozici bloky klienta, můžete chtít definovat _CRTDBG_MAP_ALLOC. _CRTDBG_MAP_ALLOC způsobí, že se všechna volání přidělení haldy mapují na jejich ekvivalenty ladění v buildech ladění. Umožňuje ukládání informací o názvu souboru a čísle řádku o každém volání přidělení v odpovídající hlavičce bloku.

_CRT_BLOCK
Bloky paměti přidělené interně mnoha funkcemi knihovny runtime jsou označené jako bloky CRT, aby je bylo možné zpracovat samostatně. V důsledku toho může detekce úniku a další operace zůstat nedotčené. Přidělení nesmí nikdy přidělit, relokovat ani uvolnit žádný blok typu CRT.

_CLIENT_BLOCK
Aplikace může sledovat konkrétní skupinu přidělení pro účely ladění tím, že je přidělí jako tento typ bloku paměti pomocí explicitních volání funkcí haldy ladění. MFC například přiděluje všechny CObject objekty jako klientské bloky; jiné aplikace můžou uchovávat různé paměťové objekty v blocích klienta. Podtypy bloků klienta lze také zadat pro větší členitost sledování. Chcete-li zadat podtypy bloků klienta, posunujte číslo doleva o 16 bitů a OR s _CLIENT_BLOCKním . Příklad:

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

Funkci háku zadanou klientem pro výpis objektů uložených v blocích klienta lze nainstalovat pomocí _CrtSetDumpClienta bude volána při každém výpisu bloku klienta ladicí funkcí. _CrtDoForAllClientObjects Lze také použít k volání dané funkce poskytované aplikací pro každý blok klienta v haldě ladění.

_FREE_BLOCK
Bloky, které jsou uvolněny, se obvykle odeberou ze seznamu. Pokud chcete zkontrolovat, jestli není uvolněná paměť zapsaná nebo chcete simulovat podmínky nedostatku paměti, můžete ponechat volné bloky v propojeném seznamu označené jako Volné a vyplnit známou bajtovou hodnotou (aktuálně 0xDD).

_IGNORE_BLOCK
V určitém intervalu je možné vypnout operace haldy ladění. Během této doby se bloky paměti uchovávají v seznamu, ale jsou označené jako ignorované bloky.

Chcete-li určit typ a podtyp daného bloku, použijte funkci _CrtReportBlockType a makra _BLOCK_TYPE a _BLOCK_SUBTYPE. Makra jsou definována <crtdbg.h> takto:

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

Kontrola integrity haldy a nevracení paměti

K mnoha funkcím haldy ladění je potřeba přistupovat z kódu. Následující část popisuje některé funkce a jejich použití.

_CrtCheckMemory
Volání můžete použít _CrtCheckMemorynapříklad ke kontrole integrity haldy v libovolném okamžiku. Tato funkce kontroluje každý blok paměti v haldě. Ověří platnost informací hlavičky bloku paměti a potvrdí, že vyrovnávací paměti nebyly změněny.

_CrtSetDbgFlag
Můžete řídit, jak halda ladění sleduje přidělení pomocí interního příznaku, _crtDbgFlagkterý lze číst a nastavit pomocí _CrtSetDbgFlag funkce. Změnou tohoto příznaku můžete instruovat haldu ladění, aby při ukončení programu zkontrolovala nevracení paměti a ohlásila případné nevrácené informace. Podobně můžete haldě říct, aby nechala volné paměťové bloky v propojeném seznamu, aby simulovala situace s nedostatkem paměti. Při kontrole haldy jsou tyto uvolněné bloky kontrolovány v celém rozsahu, aby se zajistilo, že nebyly narušeny.

Příznak _crtDbgFlag obsahuje následující bitová pole:

Bitové pole Default value Popis
_CRTDBG_ALLOC_MEM_DF V den Zapne přidělení ladění. Když je tento bit vypnutý, přidělení zůstávají zřetězený dohromady, ale jejich typ bloku je _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF Vypnout Zabraňuje uvolnění paměti, jako při simulaci podmínek nedostatku paměti. Když je tento bit zapnutý, bloky uvolněné se uchovávají v propojeném seznamu haldy ladění, ale jsou označené jako _FREE_BLOCK a vyplněny speciální bajtovou hodnotou.
_CRTDBG_CHECK_ALWAYS_DF Vypnout Příčiny _CrtCheckMemory , které se mají volat při každém přidělení a uvolnění. Spouštění je pomalejší, ale zachytává chyby rychle.
_CRTDBG_CHECK_CRT_DF Vypnout Způsobí, že bloky označené jako typ _CRT_BLOCK se zahrnou do operací detekce úniku a rozdílu stavu. Když je tento bit vypnutý, paměť používaná interně knihovnou za běhu se během těchto operací ignoruje.
_CRTDBG_LEAK_CHECK_DF Vypnout Způsobí, že kontrola úniku se provede při ukončení programu prostřednictvím volání _CrtDumpMemoryLeaks. Pokud se aplikaci nepodařilo uvolnit veškerou přidělenou paměť, vygeneruje se zpráva o chybě.

Konfigurace haldy ladění

Všechna volání funkcí haldy, jako mallocjsou , , reallocfreecalloc, newa delete vyřešit ladění verzí těchto funkcí, které pracují v haldě ladění. Když uvolníte blok paměti, halda ladění automaticky zkontroluje integritu vyrovnávacích pamětí na obou stranách přidělené oblasti a v případě, že dojde k přepsání, vydá zprávu o chybě.

Použití haldy ladění

  • Propojte sestavení ladění aplikace s ladicí verzí knihovny modulu runtime jazyka C.

Změna jednoho nebo více _crtDbgFlag bitových polí a vytvoření nového stavu příznaku

  1. Volání _CrtSetDbgFlag s parametrem newFlag nastaveným na _CRTDBG_REPORT_FLAG (k získání aktuálního _crtDbgFlag stavu) a uložení vrácené hodnoty do dočasné proměnné.

  2. Zapněte všechny bity pomocí bitového | operátoru ("nebo") na dočasné proměnné s odpovídajícími maskami bitů (reprezentovanými v kódu aplikace konstantami manifestu).

  3. Vypněte ostatní bity pomocí bitového & operátoru ("and") u proměnné s bitovým ~ operátorem ("not" nebo doplňkem) příslušných bitové masky.

  4. Volání _CrtSetDbgFlag s parametrem newFlag nastaveným na hodnotu uloženou v dočasné proměnné pro vytvoření nového stavu pro _crtDbgFlag.

    Například následující řádky kódu umožňují automatickou detekci úniku a zakažte kontroly bloků typu _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, deletea _CLIENT_BLOCK přidělení v haldě ladění jazyka C++

Ladicí verze knihovny runtime jazyka C obsahují ladicí verze jazyka C++ new a delete operátorů. Pokud používáte _CLIENT_BLOCK typ přidělení, musíte volat ladicí verzi operátoru new přímo nebo vytvořit makra, která nahradí new operátor v režimu ladění, jak je znázorněno v následujícím příkladu:

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

Ladicí verze operátoru delete funguje se všemi typy bloků a při kompilaci verze vydané verze nevyžaduje žádné změny v programu.

Funkce generování sestav stavu haldy

Pokud chcete zachytit souhrnný snímek stavu haldy v daném okamžiku, použijte strukturu definovanou _CrtMemState v <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;

Tato struktura uloží ukazatel na první (naposledy přidělený) blok v propojeném seznamu haldy ladění. Potom ve dvou polích zaznamenává, kolik z každého typu bloku paměti (_NORMAL_BLOCK_CLIENT_BLOCK_FREE_BLOCK, atd.) je v seznamu a počet bajtů přidělených v každém typu bloku. Nakonec zaznamenává nejvyšší počet bajtů přidělených v haldě jako celek až do tohoto bodu a počet bajtů, které jsou aktuálně přiděleny.

Další funkce generování sestav CRT

Následující funkce hlásí stav a obsah haldy a používají informace k detekci nevracení paměti a dalších problémů.

Function Popis
_CrtMemCheckpoint Uloží snímek haldy ve _CrtMemState struktuře poskytované aplikací.
_CrtMemDifference Porovná dvě struktury stavu paměti, uloží rozdíl mezi nimi ve třetí struktuře stavu a vrátí hodnotu PRAVDA, pokud jsou dva stavy odlišné.
_CrtMemDumpStatistics Vysadí danou _CrtMemState strukturu. Struktura může v daném okamžiku obsahovat snímek stavu haldy ladění nebo rozdíl mezi dvěma snímky.
_CrtMemDumpAllObjectsSince Výpisy informací o všech objektech přidělených od pořízení daného snímku haldy nebo od začátku spuštění. Pokaždé, když vysadí _CLIENT_BLOCK blok, volá funkci háku dodávanou aplikací, pokud byla nainstalována pomocí _CrtSetDumpClient.
_CrtDumpMemoryLeaks Určuje, zda došlo k nevracení paměti od spuštění programu, a pokud ano, výpis všech přidělených objektů. Pokaždé, když _CrtDumpMemoryLeaks vysadí _CLIENT_BLOCK blok, volá funkci háku dodávanou aplikací, pokud byla nainstalována pomocí _CrtSetDumpClient.

Sledování žádostí o přidělení haldy

Znalost názvu zdrojového souboru a čísla řádku makra kontrolního výrazu nebo generování sestav je často užitečná při vyhledání příčiny problému. Totéž není tak pravděpodobné, že platí funkce přidělení haldy. I když do stromu logiky aplikace můžete vkládat makra v mnoha vhodných bodech, přidělení se často zachytá do funkce, která se volá z mnoha různých míst v mnoha různých časech. Otázka není to, jaký řádek kódu udělal chybné přidělení. Místo toho je to jedna z tisíců přidělení provedených tímto řádkem kódu špatná a proč.

Jedinečná čísla žádostí o přidělení a _crtBreakAlloc

Existuje jednoduchý způsob, jak identifikovat konkrétní volání přidělení haldy, které se nepovedlo. Využívá jedinečné číslo žádosti o přidělení přidružené ke každému bloku v haldě ladění. Pokud jsou informace o bloku hlášeny jednou z funkcí výpisu paměti, toto číslo žádosti o přidělení je uzavřeno do složených závorek (například "{36}").

Jakmile znáte číslo žádosti o přidělení nesprávně přiděleného bloku, můžete toto číslo _CrtSetBreakAlloc předat a vytvořit zarážku. Provádění se přeruší těsně před přidělením bloku a můžete se vrátit zpět, abyste zjistili, jaká rutina byla zodpovědná za chybné volání. Abyste se vyhnuli překompilování, můžete provést totéž v ladicím programu nastavením _crtBreakAlloc na číslo žádosti o přidělení, které vás zajímá.

Vytváření ladicích verzí rutin přidělování

Složitějším přístupem je vytvořit ladicí verze vlastních rutin přidělování, které jsou srovnatelné s _dbg verzemi funkcí přidělování haldy. Potom můžete předat argumenty zdrojového souboru a čísla řádku do základních rutin přidělení haldy a okamžitě uvidíte, odkud pochází chybné přidělení.

Předpokládejme například, že vaše aplikace obsahuje běžně používanou rutinu podobnou následujícímu příkladu:

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

Do souboru hlavičky můžete přidat kód, například následující příklad:

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

Dále můžete změnit přidělení v rutině vytváření záznamů následujícím způsobem:

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

Teď bude název zdrojového souboru a číslo řádku, kde addNewRecord bylo voláno, uloženy v každém výsledném bloku přiděleném v haldě ladění a budou hlášeny při kontrole daného bloku.

Viz také

Ladění nativního kódu