Sdílet prostřednictvím


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;
};

void 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 newnebo 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

  1. Nastavte zarážku blízko začátku aplikace a spusťte ladění.

  2. 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).

  3. V okně Kukátko zadejte _crtBreakAllocsloupec 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ý.

  4. 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.

  5. 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ů.

Viz také