Sdílet prostřednictvím


Techniky ladění MFC

Pokud ladíte program MFC, můžou být tyto techniky ladění užitečné.

AfxDebugBreak

MFC poskytuje speciální funkci AfxDebugBreak pro pevné kódování zarážek ve zdrojovém kódu:

AfxDebugBreak( );

Na platformách Intel AfxDebugBreak dochází k chybě v následujícím kódu, která se objeví ve zdrojovém kódu, místo ve kódu jádra.

_asm int 3

Na jiných platformách pouze AfxDebugBreak volá DebugBreak.

Nezapomeňte odebrat AfxDebugBreak při vytváření sestavení vydané verze, nebo pro jejich obklopení použijte #ifdef _DEBUG.

Makro TRACE

Chcete-li zobrazit zprávy z programu v okně Výstup ladicího programu, můžete použít makro ATLTRACE nebo makro MFC TRACE . Podobně jako kontrolní výrazy jsou makra trasování aktivní pouze ve verzi Ladění programu a při kompilaci ve verzi vydané verze zmizí.

Následující příklady ukazují některé způsoby použití makra TRACE . Podobně jako printfmakro TRACE dokáže zpracovat několik argumentů.

int x = 1;
int y = 16;
float z = 32.0;
TRACE( "This is a TRACE statement\n" );

TRACE( "The value of x is %d\n", x );

TRACE( "x = %d and y = %d\n", x, y );

TRACE( "x = %d and y = %x and z = %f\n", x, y, z );

Makro TRACE správně zpracovává parametry char* i wchar_t*. Následující příklady ukazují použití makra TRACE společně s různými typy řetězcových parametrů.

TRACE( "This is a test of the TRACE macro that uses an ANSI string: %s %d\n", "The number is:", 2);

TRACE( L"This is a test of the TRACE macro that uses a UNICODE string: %s %d\n", L"The number is:", 2);

TRACE( _T("This is a test of the TRACE macro that uses a TCHAR string: %s %d\n"), _T("The number is:"), 2);

Další informace o makrech TRACE najdete v tématu Diagnostické služby.

Detekce úniků paměti v MFC

MFC poskytuje třídy a funkce pro detekci paměti, která byla přidělena, ale nebyla nikdy uvolněna.

Sledování přidělení paměti

V prostředí MFC můžete místo nového operátoru použít DEBUG_NEW makra k vyhledání nevrácené paměti. V ladicí verzi programu DEBUG_NEW sleduje názvy souborů a čísla řádků pro každý objekt, který přiděluje. Při kompilaci verze Release vašeho programu se DEBUG_NEW vyhodnotí jako jednoduchá new operace bez názvu souboru a informací o čísle řádku. Proto ve vydané verzi vašeho programu není žádná rychlostní penalizace.

Pokud nechcete přepsat celý program tak, aby používal DEBUG_NEW místo nového, můžete toto makro definovat ve zdrojových souborech:

#define new DEBUG_NEW

Když provedete výpis objektu, každý objekt přidělený pomocí DEBUG_NEW zobrazí soubor a číslo řádku, kde byl přidělen, což vám umožní přesně určit zdroje úniků paměti.

Ladicí verze frameworku MFC používá DEBUG_NEW automaticky, ale váš kód nikoliv. Pokud chcete výhody DEBUG_NEW, musíte použít DEBUG_NEW explicitněnebo #define new, jak je znázorněno výše.

Povolení diagnostiky paměti

Než budete moct používat diagnostické zařízení paměti, musíte povolit diagnostické trasování.

Povolení nebo zakázání diagnostiky paměti

  • Pro volejte globální funkci AfxEnableMemoryTracking pro povolení nebo zakázání diagnostického alokátoru paměti. Vzhledem k tomu, že diagnostika paměti je ve výchozím nastavení v knihovně ladění zapnutá, obvykle tuto funkci použijete k jejich dočasnému vypnutí, což zvyšuje rychlost spouštění programu a snižuje diagnostický výstup.

    Výběr konkrétních funkcí diagnostiky paměti pomocí afxMemDF

  • Pokud chcete přesnější kontrolu nad funkcemi diagnostiky paměti, můžete selektivně zapnout a vypnout jednotlivé diagnostické funkce paměti nastavením hodnoty globální proměnné MFC afxMemDF. Tato proměnná může mít následující hodnoty zadané výčtem typu afxMemDF.

    Hodnota Popis
    allocMemDF Zapněte alokátor diagnostické paměti (výchozí).
    delayFreeMemDF Zpoždění uvolnění paměti při volání delete nebo free do ukončení programu. To způsobí, že program přidělí maximální možnou velikost paměti.
    checkAlwaysMemDF Zavolejte AfxCheckMemory pokaždé, když je paměť přidělena nebo uvolněna.

    Tyto hodnoty lze použít v kombinaci provedením logické operace OR, jak je znázorněno zde:

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

Pořizování snímků paměti

  1. Vytvořte objekt CMemoryState a zavolejte CMemoryState::Checkpoint členské funkce. Tím se vytvoří první snímek paměti.

  2. Jakmile váš program provede operace přidělení a uvolnění paměti, vytvořte další objekt CMemoryState a volejte Checkpoint pro tento objekt. Tím se získá druhý snímek využití paměti.

  3. Vytvořte třetí CMemoryState objekt a zavolejte jeho členskou funkci CMemoryState::Difference, přičemž jako argumenty použijte dva předchozí CMemoryState objekty. Pokud je mezi těmito dvěma stavy paměti rozdíl, Difference vrátí funkce nenulovou hodnotu. To znamená, že některé bloky paměti nebyly dealokovány.

    Tento příklad ukazuje, jak kód vypadá:

    // Declare the variables needed
    #ifdef _DEBUG
        CMemoryState oldMemState, newMemState, diffMemState;
        oldMemState.Checkpoint();
    #endif
    
        // Do your memory allocations and deallocations.
        CString s("This is a frame variable");
        // The next object is a heap object.
        CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
    
    #ifdef _DEBUG
        newMemState.Checkpoint();
        if( diffMemState.Difference( oldMemState, newMemState ) )
        {
            TRACE( "Memory leaked!\n" );
        }
    #endif
    

    Všimněte si, že příkazy pro kontrolu paměti jsou uzavřeny v blocích #ifdef _DEBUG / #endif, takže jsou kompilovány pouze v ladicích verzích programu.

    Teď, když víte, že existuje únik paměti, můžete použít jinou členskou funkci, CMemoryState::DumpStatistics, která vám pomůže jej najít.

Zobrazení statistiky paměti

Funkce CMemoryState::Difference prohlíží dva objekty stavu paměti a detekuje všechny objekty, které nebyly uvolněny z haldy mezi počátečním a koncovým stavem. Po pořízení snímků paměti a jejich porovnání pomocí CMemoryState::Difference, můžete použít CMemoryState::DumpStatistics, abyste získali informace o objektech, které nebyly uvolněny.

Podívejte se na následující příklad:

if( diffMemState.Difference( oldMemState, newMemState ) )
{
    TRACE( "Memory leaked!\n" );
    diffMemState.DumpStatistics();
}

Ukázkový výpis z příkladu vypadá takto:

0 bytes in 0 Free Blocks
22 bytes in 1 Object Blocks
45 bytes in 4 Non-Object Blocks
Largest number used: 67 bytes
Total allocations: 67 bytes

Volné bloky jsou bloky, jejichž dealokace je zpožděna, pokud bylo afxMemDF nastaveno na delayFreeMemDF.

Běžné bloky objektů zobrazené na druhém řádku zůstávají alokovány na haldě.

Bloky, které nejsou objekty, zahrnují pole a struktury přidělené new. V tomto případě byly na haldě přiděleny čtyři bloky, které nejsou objekty, ale nebyly uvolněny.

Largest number used poskytuje maximální paměť používanou programem kdykoli.

Total allocations poskytuje celkovou velikost paměti, kterou program používá.

Pořizování výpisů objektů

V programu MFC můžete použít CMemoryState::D umpAllObjectsSince k výpisu popisu všech objektů v haldě, které nebyly uvolněny. DumpAllObjectsSince vyprázdní všechny objekty přidělené od posledního objektu CMemoryState::Checkpoint. Pokud nedošlo k žádnému Checkpoint volání, DumpAllObjectsSince vypíše všechny objekty a neobjekty, které jsou aktuálně v paměti.

Poznámka:

Před použitím dumpingu objektů MFC je nutné povolit trasování diagnostiky.

Poznámka:

MFC při ukončení programu automaticky vy dumpuje všechny nevracené objekty, takže v tomto okamžiku nemusíte vytvářet kód pro výpis objektů.

Následující kód testuje nevracení paměti porovnáním dvou stavů paměti a výpisem všech objektů v případě zjištění nevracení.

if( diffMemState.Difference( oldMemState, newMemState ) )
{
    TRACE( "Memory leaked!\n" );
    diffMemState.DumpAllObjectsSince();
}

Obsah výpisu paměti vypadá takto:

Dumping objects ->

{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4

Last Name: Smith
First Name: Alan
Phone #: 581-0215

{1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long

Čísla ve složených závorkách na začátku většiny řádků určují pořadí, ve kterém byly objekty přiděleny. Poslední přidělený objekt má nejvyšší číslo a zobrazí se v horní části výpisu.

Pokud chcete získat maximální množství informací z výpisu objektu, můžete přepsat Dump členskou funkci libovolného CObject odvozeného objektu a přizpůsobit výpis objektu.

Zarážku pro konkrétní přidělení paměti můžete nastavit nastavením globální proměnné _afxBreakAlloc na číslo zobrazené ve složených závorkách. Pokud program znovu spustíte, ladicí program přeruší provádění, když se toto přidělení provede. Pak se můžete podívat na zásobník volání a zjistit, jak se váš program do tohoto okamžiku dostal.

Knihovna runtime jazyka C má podobnou funkci _CrtSetBreakAlloc, kterou můžete použít pro přidělování za běhu jazyka C.

Interpretace výpisů paměti

Podrobnější informace najdete v tomto výpisu stavu objektu:

{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4

Last Name: Smith
First Name: Alan
Phone #: 581-0215

{1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long

Program, který vygeneroval tento výpis paměti, měl pouze dvě explicitní přidělení – jedno v zásobníku a jedno v haldě:

// Do your memory allocations and deallocations.
CString s("This is a frame variable");
// The next object is a heap object.
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );

Konstruktor CPerson přijímá tři argumenty, které jsou ukazateli na char a používají se k inicializaci členských proměnných CString. Ve výpisu paměti můžete vidět objekt CPerson spolu se třemi neobjektovými bloky (3, 4 a 5). Tyto znaky obsahují znaky pro CString členské proměnné a při vyvolání destruktoru objektu CPerson se neodstraní.

Blok číslo 2 je samotný CPerson objekt. $51A4 představuje adresu bloku a následuje obsah objektu, který byl výstupem CPerson::Dump při zavolání DumpAllObjectsSince.

Můžete uhodnout, že číslo bloku 1 je přidruženo k CString proměnné rámce z důvodu pořadového čísla a velikosti, která odpovídá počtu znaků v proměnné rámce CString . Proměnné přidělené na rámci se automaticky uvolní, když rámec přejde mimo rozsah.

Rámcové proměnné

Obecně byste se neměli starat o objekty haldy přidružené k proměnným rámce, protože se automaticky uvolní, když proměnné rámce přejdou mimo rozsah. Abyste se vyhnuli nepotřebným výpisům paměti diagnostických výpisů paměti, měli byste volání Checkpoint umístit tak, aby byly mimo rozsah proměnných rámce. Můžete například umístit závorky oboru kolem předchozího kódu přidělení, jak je znázorněno tady:

oldMemState.Checkpoint();
{
    // Do your memory allocations and deallocations ...
    CString s("This is a frame variable");
    // The next object is a heap object.
    CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
}
newMemState.Checkpoint();

Při použití závorek oboru je výpis paměti pro tento příklad následující:

Dumping objects ->

{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4

Last Name: Smith
First Name: Alan
Phone #: 581-0215

Přidělení bez objektů

Všimněte si, že některé alokace jsou objekty (například CPerson), a některé jsou neobjektové alokace. "Bezobjektové přidělení zahrnuje přidělení pro objekty, které nejsou odvozeny z CObject, nebo přidělení primitivních typů jazyka C, jako char, int, nebo long." Pokud třída odvozená od CObject přiděluje další prostor, například pro interní vyrovnávací paměti, tyto objekty zobrazí jak přidělení objektů, tak přidělení neobjektů.

Zabránění únikům paměti

Všimněte si v kódu výše, že blok paměti přidružený k proměnné rámce byl uvolněn automaticky a nezobrazuje se jako paměťový únik. Automatické uvolnění přidružené k pravidlům oborů řeší většinu úniků paměti spojených s rámcovými proměnnými.

Pro objekty přidělené na haldě však musíte objekt explicitně odstranit, aby se zabránilo úniku paměti. Pokud chcete vyčistit poslední únik paměti v předchozím příkladu, odstraňte objekt CPerson přidělený haldě následujícím způsobem:

{
    // Do your memory allocations and deallocations.
    CString s("This is a frame variable");
    // The next object is a heap object.
    CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
    delete p;
}

Přizpůsobení výpisů objektů

Když odvozujete třídu z CObject, můžete přepsat Dump členskou funkci, abyste poskytli další informace, když používáte DumpAllObjectsSince k výpisu objektů do výstupního okna.

Funkce Dump zapíše textovou reprezentaci členských proměnných objektu do kontextu výpisu (CDumpContext). Kontext výpisu paměti se podobá vstupně-výstupnímu streamu. Operátor připojení (<<) můžete použít k odeslání dat do objektu CDumpContext.

Při přepsání Dump funkce byste měli nejprve volat základní třídu verze Dump výpisu obsahu objektu základní třídy. Potom vypíše textový popis a hodnotu pro každou členovou proměnnou odvozené třídy.

Dump Deklarace funkce vypadá takto:

class CPerson : public CObject
{
public:
#ifdef _DEBUG
    virtual void Dump( CDumpContext& dc ) const;
#endif

    CString m_firstName;
    CString m_lastName;
    // And so on...
};

Protože dumping objektů dává smysl pouze při ladění programu, deklarace Dump funkce je závorka s blokem #ifdef _DEBUG / #endif .

V následujícím příkladu funkce Dump nejprve volá funkci Dump pro svou základní třídu. Potom zapíše krátký popis každé členské proměnné spolu s hodnotou člena do diagnostického streamu.

#ifdef _DEBUG
void CPerson::Dump( CDumpContext& dc ) const
{
    // Call the base class function first.
    CObject::Dump( dc );

    // Now do the stuff for our specific class.
    dc << "last name: " << m_lastName << "\n"
        << "first name: " << m_firstName << "\n";
}
#endif

Je nutné zadat CDumpContext argument, který určuje, kam se výstup výpisu paměti přesune. Ladicí verze mfc poskytuje předdefinovaný CDumpContext objekt s názvem afxDump , který odesílá výstup do ladicího programu.

CPerson* pMyPerson = new CPerson;
// Set some fields of the CPerson object.
//...
// Now dump the contents.
#ifdef _DEBUG
pMyPerson->Dump( afxDump );
#endif

Zmenšení velikosti sestavení MFC Debug

Ladicí informace pro velkou aplikaci MFC můžou zabírat velké množství místa na disku. K zmenšení velikosti můžete použít jeden z těchto postupů:

  1. Znovu sestavte knihovny MFC pomocí možnosti /Z7, /Zi, /ZI (Formát informací ladění) místo /Z7. Tyto možnosti sestaví soubor jednoprogramové databáze (PDB), který obsahuje ladicí informace pro celou knihovnu, což snižuje redundanci a šetří místo.

  2. Znovu sestavte knihovny MFC bez informací o ladění (bez možnosti /Z7, /Zi, /ZI (Formát informací o ladění)). V tomto případě vám nedostatek informací o ladění zabrání v používání většiny funkcí ladicího programu v rámci kódu knihoven MFC, ale protože knihovny MFC jsou již důkladně otestovány, nemusí to být problém.

  3. Sestavte si vlastní aplikaci s informacemi o ladění pro vybrané moduly, jak je popsáno níže.

Sestavení aplikace MFC s informacemi o ladění pro vybrané moduly

Vytváření vybraných modulů pomocí ladicích knihoven MFC umožňuje používat krokování a další ladicí nástroje v těchto modulech. Tento postup využívá konfiguraci ladění i vydané verze projektu, a proto vyžaduje změny popsané v následujících krocích (a také provedení "opětovného sestavení" nezbytného v případě, že je vyžadováno úplné sestavení vydané verze).

  1. V Průzkumníku řešení vyberte projekt.

  2. V nabídce Zobrazení vyberte Stránky vlastností.

  3. Nejprve vytvoříte novou konfiguraci projektu.

    1. V dialogovém okně <Stránky vlastností projektu> klikněte na tlačítko Configuration Manager.

    2. V dialogovém okně Configuration Manageru vyhledejte projekt v mřížce. Ve sloupci Konfigurace vyberte <Nový...>.

    3. V dialogovém okně Konfigurace nového projektu zadejte název nové konfigurace, například Částečné ladění, do pole Název konfigurace projektu .

    4. V seznamu Kopírovat nastavení zvolte Release (Uvolnit).

    5. Kliknutím na tlačítko OK zavřete dialogové okno Konfigurace nového projektu .

    6. Zavřete dialogové okno Configuration Manageru .

  4. Teď nastavíte možnosti pro celý projekt.

    1. V dialogovém okně Stránky vlastností vyberte ve složce Vlastnosti konfigurace kategorii Obecné .

    2. V mřížce nastavení projektu rozbalte položku Výchozí nastavení projektu (v případě potřeby).

    3. V části Výchozí nastavení projektu vyhledejte použití knihovny MFC. Aktuální nastavení se zobrazí v pravém sloupci mřížky. Klikněte na aktuální nastavení a změňte ho na Použití knihovny MFC ve statické knihovně.

    4. V levém podokně dialogového okna Stránky vlastností otevřete složku C/C++ a vyberte Preprocesor. V mřížce vlastností vyhledejte definice preprocesoru a nahraďte "NDEBUG" za "_DEBUG".

    5. V levém podokně dialogového okna Stránky vlastností otevřete složku Linker a vyberte kategorii vstupu . V mřížce vlastností vyhledejte další závislosti. V nastavení Další závislosti zadejte "NAFXCWD.LIB" a "LIBCMT".

    6. Kliknutím na tlačítko OK uložte nové možnosti sestavení a zavřete dialogové okno Stránky vlastností .

  5. V nabídce Sestavení vyberte Znovu sestavit. Tím se odeberou všechny informace o ladění z modulů, ale neovlivní knihovnu MFC.

  6. Teď musíte přidat informace o ladění zpět do vybraných modulů ve vaší aplikaci. Mějte na paměti, že můžete nastavit zarážky a provádět další funkce ladicího programu pouze v modulech, které jste zkompilovali s informacemi o ladění. Pro každý soubor projektu, ve kterém chcete zahrnout informace o ladění, proveďte následující kroky:

    1. V Průzkumníku řešení otevřete složku Zdrojové soubory , která se nachází v projektu.

    2. Vyberte soubor, pro který chcete nastavit informace o ladění.

    3. V nabídce Zobrazení vyberte Stránky vlastností.

    4. V dialogovém okně Stránky vlastností ve složce Nastavení konfigurace otevřete složku C/C++ a pak vyberte kategorii Obecné .

    5. V mřížce vlastností vyhledejte Formát informací o ladění.

    6. Klikněte na nastavení Formát informací o ladění a vyberte požadovanou možnost (obvykle /ZI) pro informace o ladění.

    7. Pokud používáte aplikaci vygenerovanou průvodcem nebo máte předkompilované hlavičky, musíte před kompilací dalších modulů vypnout předkompilované hlavičky nebo je znovu zkompilovat. V opačném případě se zobrazí upozornění C4650 a chybová zpráva C2855. Předkompilované hlavičky můžete vypnout změnou nastavení Vytvořit/Použít předkompilované hlavičky v <dialogovém okně Vlastnosti projektu> (složka Vlastnosti konfigurace, podsložka C/C++, kategorie Předkompilované hlavičky).

  7. V nabídce Sestavení vyberte Sestavit a znovu sestavte soubory projektu, které jsou zastaralé.

    Jako alternativu k technice popsané v tomto tématu můžete použít externí soubor pravidel k definování jednotlivých možností pro každý soubor. V takovém případě je nutné definovat příznak _DEBUG pro každý modul, abyste mohli propojit knihovny ladění MFC. Pokud chcete používat knihovny verzí MFC, musíte definovat NDEBUG. Další informace o psaní externích makefile najdete v referenčních informacích k nástroji NMAKE.