Sdílet prostřednictvím


Podrobnosti haldy ladění CRT

Toto téma obsahuje podrobný pohled na haldu ladění CRT.

Obsah

Najít přetečení zásobníku s laděním haldy

Typy bloků na haldě ladění

Zkontrolujte integrity haldy a nevrácenou paměť

Konfigurace haldy ladění

nové, odstranit a _CLIENT_BLOCKs v haldě ladění C++

Funkce vykazování stavu haldy

Požadavky na přidělení haldy sledování

Najít přetečení zásobníku s laděním haldy

Dva z nejběžnějších a těžko vystopovatelných problémů, které potkávají programátory jsou přepisování konce přidělené vyrovnávací paměti a nevracení paměť (neúspěšný pokus o volná přidělení, poté, co už není potřeba).Halda ladění poskytuje výkonné nástroje pro řešení problémů s přidělením paměti tohoto druhu.

Ladicí verze funkcí haldy volají standardní nebo základní verze, používané v sestaveních vydání.Při požadavku na blok paměti přidělí správce hald ladění ze základní haldy mírně větší blok paměti, než je požadován, a vrací ukazatel na vaši část tohoto bloku.Předpokládejme například, že aplikace obsahuje volání: malloc( 10 ).V sestavení pro vydání by funkce malloc volala základní rutinu přidělení haldy požadující přidělení 10 bajtů.V sestavení ladění by však funkce malloc volala funkci _malloc_dbg, která by potom volala základní rutinu přidělení haldy požadující přidělení 10 bajtů plus přibližně 36 bajtů další paměti.Všechny výsledné bloky paměti v haldě ladění jsou spojeny do jednoho propojeného seznamu a seřazeny podle toho, kdy byly přiděleny.

Další paměť přidělená rutinami haldy ladicího programu je používána pro ukládání informací, pro ukazatele, které propojují paměť bloků ladicího programu dohromady a pro malé mezipaměti na každé straně vašich dat, které zachytí přepisy přiděleného regionu.

Struktura hlavičky bloku sloužící k ukládání informací o vedení haldy ladění je v současné době deklarována tak, jak je uvedeno v souboru s hlavičkou DBGINT.H:

typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
    struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
    struct _CrtMemBlockHeader *pBlockHeaderPrev;
    char *szFileName;    // File name
    int nLine;           // Line number
    size_t nDataSize;    // Size of user block
    int nBlockUse;       // Type of block
    long lRequest;       // Allocation number
// Buffer just before (lower than) the user's memory:
    unsigned char gap[nNoMansLandSize];
} _CrtMemBlockHeader;

/* In an actual memory block in the debug heap,
 * this structure is followed by:
 *   unsigned char data[nDataSize];
 *   unsigned char anotherGap[nNoMansLandSize];
 */

Vyrovnávací paměti NoMansLand po obou stranách oblasti dat uživatele bloku jsou aktuálně 4 bajty a jsou vyplněny hodnotou známého bajtu používaného rutinami ladění haldy k ověření, že omezení uživatele bloku paměti nebyly přepsány.Halda ladění také doplní nové bloky paměti se známou hodnotou.Pokud se rozhodnete ponechat uvolněné bloky v propojeném seznamu haldy, jak je popsáno níže, tyto uvolněné bloky budou také zaplněny známou hodnotou.Aktuálně jsou skutečně používané bajtové hodnoty následující:

  • NoMansLand (0xFD)
    "NoMansLand" využívá vyrovnávací paměť po obou stranách paměti používané podle aplikací, které jsou nyní vyplněny 0xFD.

  • Uvolněné bloky (0xDD)
    Uvolněné bloky jsou uchovávané nepoužívané v ladění propojeného seznamu haldy, když příznak _CRTDBG_DELAY_FREE_MEM_DF je nastaven, jsou nyní vyplněny 0xDD.

  • Nové objekty (0xCD)
    Nové objekty jsou vyplněny hodnotou 0xCD při přidělování.

Zpět na začátekObsah

Typy bloků na haldě ladění

Každý blok paměti v haldě ladění je přiřazen k jednomu z pěti typů rozdělení.Tyto typy jsou sledovány a jinak hlášeny pro účely detekce nevrácení a vykazování stavu.Můžete zadat typ bloku přidělením pomocí přímého volání jedné z funkcí přidělení haldy ladění, jako je _malloc_dbg.Pět typů bloků paměti haldy ladění (nastaveno v členovi nBlockUse struktury _CrtMemBlockHeader) jsou následující:

  • _NORMAL_BLOCK
    Volání malloc nebo calloc vytvoří blok Normal.Pokud máte v úmyslu používat pouze normální bloky a nepotřebujete bloky klienta, je vhodné definovat příznak _CRTDBG_MAP_ALLOC, což způsobí, že budou všechna volání o přidělení haldy namapována na své ekvivalenty ladění v sestaveních ladění.To vám umožní uložení údajů o názvu souboru a číslu řádku každého volání přidělení v odpovídajícím záhlaví bloku.

  • _CRT_BLOCK
    Bloky paměti přidělené interně mnoha funkcemi knihovny run-time jsou označeny jako CRT bloky tak, aby mohly být zpracovány samostatně.V důsledku toho dochází k detekci přetečení a jiné operace nemusí být ovlivněny.Přidělení nesmí nikdy přidělit, přerozdělit nebo uvolnit jakýkoli blok typu CRT.

  • _CLIENT_BLOCK
    Aplikace může zvlášť sledovat danou skupinu přidělení pro účely ladění jejich přidělením jako tento typ bloku paměti pomocí explicitního volání funkcí haldy ladění.Knihovny MFC například přidělují všechny objekty CObjects jako bloky klienta. Jiné aplikace mohou mít v blocích klienta jiné paměťové objekty.Podtypy bloků klienta lze také zadat pro větší rozlišovací schopnost sledování.Chcete-li určit podtypy bloků klienta, posuňte číslo doleva o 16 bitů a použijte OR s _CLIENT_BLOCK.Příklad:

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

    Funkci háčku dodaná klientem pro výpis objektů uložených v blocích klienta lze nainstalovat pomocí _CrtSetDumpClient, pak bude volána vždy, když bude blok klienta vypsán pomocí funkce ladění.Také lze použít _CrtDoForAllClientObjects k volání dané funkce poskytnuté aplikací pro každý blok Klient v haldě ladění.

  • _FREE_BLOCK
    Bloky, které jsou uvolněny, jsou obvykle odebrány ze seznamu.Chcete-li zkontrolovat, že do uvolněné paměti není stále zapisováno, nebo simulovat podmínky nedostatku paměti, můžete zachovat uvolněné bloky v propojeném seznamu, kde jsou označeny jako volné a se známou bajtovou hodnotou (nyní 0xDD).

  • _IGNORE_BLOCK
    Je možné vypnout operace haldy ladění na určité časové období.Během této doby jsou bloky paměti uloženy v seznamu, ale jsou označeny jako bloky Ignorovat.

Chcete-li zjistit typ a podtyp daného bloku, použijte funkci _CrtReportBlockType a makra _BLOCK_TYPE a _BLOCK_SUBTYPE.Makra jsou definována (v crtdbg.h) následovně:

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

Zpět na začátekObsah

Zkontrolujte těsnost integrity a paměť haldy

K mnoha funkcím haldy ladění je třeba přistupovat zevnitř vašeho kódu.Následující část uvádí některé funkce a jejich použití.

  • _CrtCheckMemory
    Můžete použít volání _CrtCheckMemory, například pro kontrolu integrity haldy v libovolném bodě.Tato funkce zkontroluje každý blok paměti v haldě, ověří, zda jsou platné informace záhlaví bloku paměti, a potvrdí, že vyrovnávací paměti nebyly změněny.

  • _CrtSetDbgFlag
    Můžete řídit, jak bude halda ladění sledovat přidělení pomocí vnitřního příznaku _crtDbgFlag, který lze přečíst a nastavit pomocí funkce _CrtSetDbgFlag.Změníte-li tento příznak, dáte pokyn haldě ladění, aby spustila kontrolu nevracení paměti při ukončení programu a podávala hlášení zjištěných případů.Podobně můžete určit, že uvolněné bloky paměti nesmí být odebrány ze seznamu propojených aby simulovaly situace nedostatku paměti.Při kontrole haldy tyto uvolněné bloky jsou kontrolovány celé k zajištění toho, aby nebyly narušeny.

    Příznak _CrtDbgFlag obsahuje následující pole bit:

    Pole bitů

    Výchozí

    hodnota

    Description

    _CRTDBG_ALLOC_MEM_DF

    Zapnuto

    Zapne předělení ladění.Když je tento bit vypnutý, zůstane přidělení zřetězeno, ale jejich typ bloku je _IGNORE_BLOCK.

    _CRTDBG_DELAY_FREE_MEM_DF

    Vypnuto

    Zabraňuje skutečnému uvolňování paměti, například v případě simulace nedostatku paměti.Pokud je tento bit zapnutý, uvolněné bloky jsou uchovávány v propojeném seznamu haldy ladění, ale jsou označeny jako _FREE_BLOCK a vyplněny zvláštní bajtovou hodnotou.

    _CRTDBG_CHECK_ALWAYS_DF

    Vypnuto

    Způsobí volání _CrtCheckMemory při každé alokaci a dealokaci.Toto zpomalí provádění, ale rychle zachytí chyby.

    _CRTDBG_CHECK_CRT_DF

    Vypnuto

    Způsobí, že bloky označené jako typ _CRT_BLOCK budou zahrnuty při detekci nevrácení a zjišťování rozdílu stavu.Když je tento bit vypnutý, paměť používaná interně knihovnou run-time je ignorována během těchto operací.

    _CRTDBG_LEAK_CHECK_DF

    Vypnuto

    Způsobuje spuštění kontroly nevracení při ukončení programu prostřednictvím volání do _CrtDumpMemoryLeaks.Zpráva o chybě je generována, pokud se aplikaci nepodaří uvolnění veškeré přidělené paměti.

Zpět na začátekObsah

Konfigurace ladění haldy

Všechna volání funkce haldy jako malloc, free, calloc, realloc, new a delete vedou k verzím ladění a těm funkcím, které pracují v haldě ladění.Při uvolnění bloku paměti ladění haldy automaticky kontroluje integritu na obou stranách přidělené oblasti vyrovnávací paměti a vydá zprávu o chybě v případě, že došlo k přepsání.

Použití haldy ladění

  • Propojte sestavení ladění aplikace s ladicí verzí knihovny run-time C.

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

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

  2. Zapněte všechny bity ORováním (bitový symbol |) dočasné proměnné s odpovídajícími bitovými maskami (představovanými v kódu aplikace konstantami manifestu).

  3. Vypněte ostatní bity ANDováním (bitový symbol &) proměnné s NOT (bitový symbol ~) vhodných bitových masek.

  4. Volejte _CrtSetDbgFlag s parametrem newFlag nastaveným na hodnotu uloženou v dočasné proměnné a vytvořte tak nový stav pro _crtDbgFlag.

Například následující řádky kódu aktivují automatickou detekci nevrácení paměti a vypnou kontrolu 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 );

Zpět na začátekObsah

nové, odstranit a _CLIENT_BLOCKs v haldě ladění C++

Ladicí verze knihovny run-time jazyka C obsahuje ladicí verze operátorů new a delete jazyka C++.Použijete-li typ přidělení _CLIENT_BLOCK, musíte přímo zavolat ladicí verzi operátoru new nebo vytvořit makra, která nahradí operátor new 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 pracuje se všemi typy bloků a nevyžaduje žádné změny v programu při kompilaci verze vydání.

Zpět na začátekObsah

Funkce vykazování stavu haldy

_CrtMemState

Chcete-li zachytit souhrnný snímek stavu haldy v daném okamžiku, použijte strukturu _CrtMemState definovanou 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 ukládá ukazatel na první blok (poslední přidělený) v propojeném seznamu haldy ladění.Potom ve dvou polích zaznamenává, kolik jednotlivých typů paměťových bloků (_NORMAL_BLOCK, _CLIENT_BLOCK, _FREE_BLOCK a tak dále) je uvedeno v seznamu a počet bajtů alokovaných v každém druhu bloku.Nakonec zaznamená nejvyšší počet bajtů alokovaných v haldě jako celek až k danému bodu, a počet bajtů, který je aktuálně přidělen.

Ostatní funkce vykazování CRT

Následující funkce vykazuje sestavy stavu a obsahu haldy a využívá tyto informace pro zjištění nevracení paměti a dalších problémů.

Funkce

Description

_CrtMemCheckpoint

Uloží snímek haldy ve struktuře _CrtMemState poskytnuté aplikací.

_CrtMemDifference

Porovná dvě struktury stavu paměti, ukládá rozdíly mezi nimi ve třetí struktuře stavu a pokud se oba stavy liší, vrátí hodnotu TRUE.

_CrtMemDumpStatistics

Vypíše danou strukturu _CrtMemState.Struktura může obsahovat snímek stavu ladění haldy v daném okamžiku nebo rozdíl mezi dvěma snímky.

_CrtMemDumpAllObjectsSince

Vypíše informace o všech objektech, které byly přiděleny od pořízení daného snímku v rámci haldy, nebo od začátku spuštění.Pokaždé, když vypíše blok _CLIENT_BLOCK, volá funkci připojení poskytnutou aplikací za předpokladu, že byla nainstalována pomocí _CrtSetDumpClient.

_CrtDumpMemoryLeaks

Určuje, zda od spuštění programu došlo k nevrácení paměti, a pokud ano, vypíše všechny přidělené objekty.Pokaždé, když _CrtDumpMemoryLeaks vypíše blok _CLIENT_BLOCK, volá funkci připojení poskytnutou aplikací za předpokladu, že byla nainstalována pomocí _CrtSetDumpClient.

Zpět na začátekObsah

Požadavky na přidělení haldy sledování

Ačkoli přesným rozpoznáním názvu zdrojového souboru a čísla řádek, na kterém se provede makro hodnocení nebo vytváření sestav, je velmi užitečné při hledání příčiny problému, neplatí to u funkcí přidělení haldy.I když lze makra vkládat na mnoho vhodných míst v logické stromové struktuře aplikace, je přidělení často ukryto ve speciální rutině, která je volána z mnoha různých míst v mnoha různých časech.Otázka obvykle není, který řádek kódu způsobil špatné přidělené, ale spíše které z tisíců přidělení provedených tímto řádkem kódu bylo chybné a proč.

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

Nejjednodušší způsob, jak identifikovat konkrétní volání přidělení haldy, které se nezdařilo, je využít jedinečného čísla požadavku přidělení u každého bloku haldy pro ladění.Pokud informace o bloku jsou vykázány jednou z funkcí s výpisem paměti, je toto číslo žádosti o přidělení uzavřeno ve složených závorkách (například „{36}“).

Jakmile budete znát číslo žádosti o přidělení nesprávně přiděleného bloku, můžete předat toto číslo funkci _CrtSetBreakAlloc, aby vytvořila zarážku.Spuštění se přeruší těsně před rozdělením bloku a vy tak můžete zpětně zjistit, jaké rutina je odpovědná za chybné volání.Aby se zabránilo opětovné kompilaci, 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í verzí ladění pro vaše rutiny přidělení

Poněkud složitějším přístupem je vytvoření verzí ladění vlastní rutiny přidělení srovnatelné s verzemi _dbgfunkcí přidělení haldy.Pak můžete předat zdrojový soubor a argumenty čísla řádku prostřednictvím základní rutiny přidělení haldy a budete okamžitě vidět, odkud pochází chybné přidělení.

Předpokládejme například, že aplikace obsahuje běžně používanou rutinu, která je podobná následující:

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

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

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

Dále lze změnit přidělování při rutině vytváření záznamu takto:

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

Nyní budou název zdrojového souboru a číslo řádku, kde byla volána funkce addNewRecord, uloženy do každého výsledného bloku přiděleného v haldě ladění a budou hlášeny po prozkoumání tohoto bloku.

Zpět na začátekObsah

Viz také

Další zdroje

Ladění nativního kódu