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_BLOCK
ní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í _CrtSetDumpClient
a 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 _CrtCheckMemory
napří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, _crtDbgFlag
který 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 | Výchozí hodnota | Popis |
---|---|---|
_CRTDBG_ALLOC_MEM_DF |
Zapnout | 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 malloc
jsou , , realloc
free
calloc
, new
a 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
Volání
_CrtSetDbgFlag
s parametremnewFlag
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é.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).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.Volání
_CrtSetDbgFlag
s parametremnewFlag
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
, delete
a _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.