Hledání nevrácené paměti pomocí knihovny CRT
Nevracení paměti patří mezi nejmítnější a obtížně rozpoznatelnější chyby v aplikacích C/C++. Nevracení paměti způsobí selhání správného uvolnění paměti, která byla dříve přidělena. Zpočátku nemusí dojít k malému nevrácení paměti, ale v průběhu času může dojít k příznakům v rozsahu nízkého výkonu až po chybové ukončení, když aplikace vyčerpá paměť. Nevracená aplikace, která využívá veškerou dostupnou paměť, může způsobit chybové ukončení jiných aplikací, což způsobí nejasnost, za kterou aplikaci zodpovídá. Dokonce i neškodné nevracení paměti může znamenat jiné problémy, které by měly být opraveny.
Ladicí program sady Visual Studio a knihovna runtime jazyka C (CRT) vám můžou pomoct při zjišťování a identifikaci nevracení paměti.
Povolení detekce nevracení paměti
Primárními nástroji pro detekci nevracení paměti jsou ladicí program C/C++ a funkce haldy ladění CRT.
Pokud chcete povolit všechny funkce haldy ladění, zahrňte do programu C++ následující příkazy v následujícím pořadí:
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
Příkaz #define
mapuje základní verzi funkcí haldy CRT na odpovídající verzi ladění. Pokud příkaz vynecháte #define
, výpis paměti nevracení bude méně podrobný.
Zahrnutí crtdbg.h mapuje malloc
a free
funkce na jejich ladicí verze a _malloc_dbg
_free_dbg
, které sledují přidělení paměti a uvolnění. K tomuto mapování dochází pouze v buildech ladění, které mají _DEBUG
. Sestavení vydaných verzí používají běžné malloc
a free
funkce.
Po povolení funkcí haldy ladění pomocí předchozích příkazů umístěte volání _CrtDumpMemoryLeaks
před výstupní bod aplikace, aby se při ukončení aplikace zobrazila zpráva o nevracení paměti.
_CrtDumpMemoryLeaks();
Pokud má vaše aplikace několik ukončení, nemusíte ručně umístit _CrtDumpMemoryLeaks
na každý výstupní bod. Pokud chcete způsobit automatické volání _CrtDumpMemoryLeaks
v každém výstupním bodě, umístěte volání na _CrtSetDbgFlag
začátek aplikace s bitovými poli zobrazenými tady:
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
Ve výchozím nastavení _CrtDumpMemoryLeaks
výstup sestavy nevrácené paměti do podokna Ladění okna Výstup . Pokud používáte knihovnu, může knihovna obnovit výstup do jiného umístění.
Sestavu můžete _CrtSetReportMode
přesměrovat do jiného umístění nebo zpět do okna Výstup , jak je znázorněno tady:
_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG );
Následující příklad ukazuje jednoduchý nevracení paměti a zobrazuje informace o nevrácené paměti pomocí _CrtDumpMemoryLeaks();
.
// debug_malloc.cpp
// compile by using: cl /EHsc /W4 /D_DEBUG /MDd debug_malloc.cpp
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
int main()
{
std::cout << "Hello World!\n";
int* x = (int*)malloc(sizeof(int));
*x = 7;
printf("%d\n", *x);
x = (int*)calloc(3, sizeof(int));
x[0] = 7;
x[1] = 77;
x[2] = 777;
printf("%d %d %d\n", x[0], x[1], x[2]);
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
_CrtDumpMemoryLeaks();
}
Interpretace sestavy nevracení paměti
Pokud vaše aplikace nedefinuje _CRTDBG_MAP_ALLOC
, _CrtDumpMemoryLeaks zobrazí zprávu o nevrácené paměti, která vypadá takto:
Detected memory leaks!
Dumping objects ->
{18} normal block at 0x00780E80, 64 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
Pokud vaše aplikace definuje _CRTDBG_MAP_ALLOC
, sestava nevracení paměti vypadá takto:
Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\leaktest\leaktest.cpp(20) : {18}
normal block at 0x00780E80, 64 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
Druhá sestava zobrazuje název souboru a číslo řádku, kde je nejprve přidělena nevracená paměť.
Bez ohledu na to, jestli definujete _CRTDBG_MAP_ALLOC
, zobrazí se sestava nevracení paměti:
- Číslo přidělení paměti, které je
18
v příkladu - Typ bloku
normal
v příkladu. - Šestnáctkové umístění
0x00780E80
paměti v příkladu. - Velikost bloku
64 bytes
v příkladu. - Prvních 16 bajtů dat v bloku v šestnáctkové formě.
Typy bloků paměti jsou normální, klient nebo CRT. Normální blok je běžná paměť přidělená programem. Blok klienta je speciální typ bloku paměti používaný programy MFC pro objekty, které vyžadují destruktor. Operátor MFC new
vytvoří buď normální blok, nebo klientský blok, podle potřeby pro objekt, který se vytváří.
Blok CRT je přidělen knihovnou CRT pro vlastní použití. Knihovna CRT zpracovává přidělení těchto bloků, takže se bloky CRT nezobrazí ve zprávě o nevrácení paměti, pokud nejsou vážné problémy s knihovnou CRT.
Existují dva další typy paměťových bloků, které se nikdy nezobrazují v sestavách nevrácení paměti. Volný blok je paměť, která byla vydána, takže definice není nevracená. Ignorovaný blok je paměť, kterou jste explicitně označili k vyloučení ze sestavy nevracení paměti.
Předchozí techniky identifikují nevracení paměti přidělené paměti pomocí standardní funkce CRT malloc
. Pokud váš program přiděluje paměť pomocí operátoru C++ new
, může se však zobrazit pouze název souboru a číslo řádku, kde operator new
volání _malloc_dbg
v sestavě nevrácené paměti. Pokud chcete vytvořit užitečnější sestavu nevrácené paměti, můžete napsat makro, jako je následující, aby se nahlásil řádek, který provedl přidělení:
#ifdef _DEBUG
#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
// Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
// allocations to be of _CLIENT_BLOCK type
#else
#define DBG_NEW new
#endif
Operátor teď můžete nahradit new
pomocí DBG_NEW
makra v kódu. V buildech ladění používá přetížení globálního operator new
systému, DBG_NEW
který přebírá další parametry pro typ bloku, soubor a číslo řádku. Přetížení volání _malloc_dbg
k zaznamenání dodatečných new
informací. Sestavy nevracení paměti zobrazují název souboru a číslo řádku, kde byly přidělovány nevracené objekty. Buildy vydaných verzí stále používají výchozí new
. Tady je příklad techniky:
// debug_new.cpp
// compile by using: cl /EHsc /W4 /D_DEBUG /MDd debug_new.cpp
#define _CRTDBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>
#ifdef _DEBUG
#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
// Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
// allocations to be of _CLIENT_BLOCK type
#else
#define DBG_NEW new
#endif
struct Pod {
int x;
};
int main() {
Pod* pPod = DBG_NEW Pod;
pPod = DBG_NEW Pod; // Oops, leaked the original pPod!
delete pPod;
_CrtDumpMemoryLeaks();
}
Když tento kód spustíte v ladicím programu sady Visual Studio, volání vygeneruje _CrtDumpMemoryLeaks
sestavu v okně Výstup , které vypadá nějak takto:
Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\debug_new\debug_new.cpp(20) : {75}
normal block at 0x0098B8C8, 4 bytes long.
Data: < > CD CD CD CD
Object dump complete.
Tento výstup hlásí, že únik přidělení byl na řádku 20 debug_new.cpp.
Poznámka:
Nedoporučujeme vytvářet makro preprocesoru s názvem new
nebo jakékoli jiné klíčové slovo jazyka.
Nastavení zarážek u čísla přidělení paměti
Číslo přidělení paměti vám řekne, kdy byl přidělen nevracený blok paměti. Blok s číslem přidělení paměti 18, například 18. blok paměti přidělený během spuštění aplikace. Sestava CRT spočítá všechny přidělení bloků paměti během běhu, včetně přidělení knihovny CRT a dalších knihoven, jako je MFC. Proto blok přidělení paměti číslo 18 pravděpodobně není 18. blok paměti přidělený vaším kódem.
Číslo přidělení můžete použít k nastavení zarážky přidělení paměti.
Nastavení zarážky přidělení paměti pomocí okna Kukátko
Nastavte zarážku blízko začátku aplikace a spusťte ladění.
Když se aplikace pozastaví na zarážce, otevřete okno Kukátko výběrem možnosti Ladit>Windows>Watch 1 (nebo Watch 2, Watch 3 nebo Watch 4).
V okně Kukátko zadejte
_crtBreakAlloc
sloupec Název.Pokud používáte multithreaded DLL verzi knihovny CRT (možnost /MD), přidejte kontextový operátor:
{,,ucrtbased.dll}_crtBreakAlloc
Ujistěte se, že jsou načteny symboly ladění.
_crtBreakAlloc
V opačném případě se označí jako neidentifikovaný.Stiskněte klávesu Enter.
Ladicí program vyhodnotí volání a umístí výsledek do sloupce Hodnota . Tato hodnota je -1 , pokud jste nenastavili žádné zarážky pro přidělení paměti.
Ve sloupci Hodnota nahraďte hodnotu číslem přidělení paměti, kde chcete ladicí program přerušit.
Po nastavení zarážky u čísla přidělení paměti pokračujte v ladění. Ujistěte se, že běží za stejných podmínek, takže se číslo přidělení paměti nezmění. Když se program přeruší při zadaném přidělení paměti, použijte okno Zásobník volání a další okna ladicího programu k určení podmínek, za kterých byla paměť přidělena. Pak můžete pokračovat v provádění a sledovat, co se stane s objektem, a určit, proč není správně uvolněn.
Může být užitečné také nastavení datové zarážky na objektu. Další informace naleznete v tématu Použití zarážek.
V kódu můžete také nastavit zarážky přidělení paměti. Můžete nastavit:
_crtBreakAlloc = 18;
nebo:
_CrtSetBreakAlloc(18);
Porovnání stavů paměti
Další technika pro vyhledání nevrácené paměti zahrnuje pořizování snímků stavu paměti aplikace v klíčových bodech. Pokud chcete pořídit snímek stavu paměti v daném bodě aplikace, vytvořte _CrtMemState
strukturu a předejte ji funkci _CrtMemCheckpoint
.
_CrtMemState s1;
_CrtMemCheckpoint( &s1 );
Funkce _CrtMemCheckpoint
vyplní strukturu snímkem aktuálního stavu paměti.
Pokud chcete vytvořit výstup obsahu _CrtMemState
struktury, předejte strukturu _ CrtMemDumpStatistics
funkci:
_CrtMemDumpStatistics( &s1 );
_CrtMemDumpStatistics
Výstupem je výpis stavu paměti, který vypadá takto:
0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
3071 bytes in 16 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 3071 bytes.
Total allocations: 3764 bytes.
Pokud chcete zjistit, jestli došlo k nevrácení paměti v části kódu, můžete pořizovat snímky stavu paměti před a za částí a pak použít _CrtMemDifference
k porovnání těchto dvou stavů:
_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );
if ( _CrtMemDifference( &s3, &s1, &s2) )
_CrtMemDumpStatistics( &s3 );
_CrtMemDifference
porovná stavy s1
paměti a s2
vrátí výsledek (s3
), který je rozdílem mezi s1
a s2
.
Jednou z technik hledání nevrácené paměti začíná umístěním _CrtMemCheckpoint
volání na začátek a konec aplikace a následným _CrtMemDifference
porovnáním výsledků. Pokud _CrtMemDifference
se zobrazí nevracení paměti, můžete přidat další _CrtMemCheckpoint
volání, která program rozdělí pomocí binárního vyhledávání, dokud izolujete zdroj nevracení.
Falešně pozitivní výsledky
_CrtDumpMemoryLeaks
může indikovat nepravdivé informace o nevracení paměti, pokud knihovna označí interní přidělení jako normální bloky místo bloků CRT nebo klientských bloků. V takovém případě _CrtDumpMemoryLeaks
není možné zjistit rozdíl mezi přiděleními uživatelů a interními přiděleními knihoven. Pokud globální destruktory přidělení knihovny běží po bodu, kde voláte _CrtDumpMemoryLeaks
, každé interní přidělení knihovny je hlášeno jako nevracení paměti. Verze standardní knihovny šablon starší než Visual Studio .NET můžou způsobit _CrtDumpMemoryLeaks
hlášení takových falešně pozitivních výsledků.