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í.
Obsah
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)
Obsah
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.
Obsah
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
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é.
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).
Vypněte ostatní bity ANDováním (bitový symbol &) proměnné s NOT (bitový symbol ~) vhodných bitových masek.
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 );
Obsah
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í.
Obsah
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 |
---|---|
Uloží snímek haldy ve struktuře _CrtMemState poskytnuté aplikací. |
|
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. |
|
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. |
|
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. |
|
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. |
Obsah
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.
Obsah