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

V tomto tématu

AfxDebugBreak

Makro TRACE

Zjišťování nevracení paměti v prostředí MFC

AfxDebugBreak

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

AfxDebugBreak( );

Na platformách AfxDebugBreak Intel vytvoří následující kód, který se rozdělí ve zdrojovém kódu místo kódu jádra:

_asm int 3

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

Příkazy nezapomeňte odebrat AfxDebugBreak při vytváření sestavení vydané verze nebo je můžete použít #ifdef _DEBUG k jejich obklopování.

V tomto tématu

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.

V tomto tématu

Zjišťování nevracení paměti v prostředí MFC

MFC poskytuje třídy a funkce pro detekci přidělené paměti, ale nikdy uvolněné.

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. Ve verzi Ladění programu sleduje název souboru a číslo řádku pro každý objekt, DEBUG_NEW který přiděluje. Při kompilaci verze programu se DEBUG_NEW přeloží na jednoduchou novou operaci bez názvu souboru a informací o čísle řádku. Proto platíte žádné rychlé penále ve verzi vydané verze programu.

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 stavu objektu, zobrazí každý objekt přidělený DEBUG_NEW soubor a číslo řádku, kde byl přidělen, což vám umožní určit zdroje nevracení paměti.

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

V tomto tématu

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

  • Volání globální funkce AfxEnableMemoryTracking povolení nebo zakázání alokátoru diagnostické 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 Volání AfxCheckMemory při každém přidělení nebo uvolnění paměti.

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

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

    V tomto tématu

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 program provede operace přidělení paměti a uvolnění paměti, vytvořte pro tento objekt další CMemoryState objekt a volejte Checkpoint ho. Tím se získá druhý snímek využití paměti.

  3. Vytvořte třetí CMemoryState objekt a zavolejte jeho CMemoryState::D ifference členské funkce, která poskytuje jako argumenty 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 uvolně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 kontroly paměti jsou závorky #ifdef _DEBUG / #endif bloky tak, aby byly kompilovány pouze ve verzích ladění programu.

    Teď, když víte, že nevracení paměti existuje, můžete použít jinou členovou funkci CMemoryState ::D umpStatistics , která vám pomůže ji najít.

    V tomto tématu

Zobrazení statistiky paměti

Funkce CMemoryState::D ifference sleduje dva objekty stavu paměti a detekuje všechny objekty, které nejsou 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::Differencemůžete volat CMemoryState::D umpStatistics získat informace o objektech, které nebyly uvolněny.

Představte si následující příklad:

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

Ukázkový výpis paměti 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ž zrušení je zpožděno, pokud afxMemDF bylo nastaveno na delayFreeMemDF.

Běžné bloky objektů zobrazené na druhém řádku zůstanou přidělené 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á.

V tomto tématu

Převzetí výpisů stavu objektu

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 vysadí všechny objekty a objekty, které nejsou 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 stavu objektu, můžete přepsat Dump členovou funkci libovolného CObjectodvozeného objektu a přizpůsobit výpis stavu 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 podívat se, 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ělení za běhu jazyka C.

V tomto tématu

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řebírá tři argumenty, které jsou ukazatele na char, které se používají k inicializaci CString členských proměnných. V výpisu paměti můžete zobrazit CPerson objekt spolu se třemi objekty bezobjektu (3, 4 a 5). Tyto znaky obsahují znaky pro CString členské proměnné a nebudou odstraněny při vyvolání destruktoru objektu CPerson .

Číslo bloku 2 je CPerson samotný 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 snímku se automaticky uvolní, když rámec přestane být vymezený.

Proměnné rámce

Obecně byste se neměli starat o objekty haldy přidružené k proměnným rámce, protože jsou automaticky uvolněny, když proměnné rámce vyjdou 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í objektů bezobjektu

Všimněte si, že některé přidělení jsou objekty (například CPerson) a některé jsou přidělení objektů, které nejsou objekty. "Přidělení objektů bezobjektu" jsou přidělení objektů, které nejsou odvozeny z CObject primitivních typů jazyka C, jako charjsou , intnebo long. Pokud CObject odvozená třída přiděluje další prostor, například pro interní vyrovnávací paměti, tyto objekty zobrazí jak objekty, tak přidělení objektu i objektu.

Zabránění nevrácené paměti

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

Pro objekty přidělené haldě však musíte objekt explicitně odstranit, aby nedošlo k nevrácení paměti. Pokud chcete vyčistit poslední nevracení paměti v předchozím příkladu, odstraňte CPerson objekt 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;
}

V tomto tématu

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

Když odvozujete třídu z objektu CObject, můžete přepsat Dump členská funkce poskytnout další informace při použití 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...
};

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

V následujícím příkladu Dump funkce nejprve volá Dump funkci 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

V tomto tématu

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 zařízení ladicího programu v kódu knihovny MFC, ale protože knihovny MFC jsou již důkladně laděné, 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.

    V tomto tématu

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

Vytváření vybraných modulů pomocí knihoven ladění MFC umožňuje používat krokování a další ladicí zařízení 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ík ř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 Verze.

    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" "_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ík řešení otevřete složku Zdrojové soubory umístěnou 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 Konfigurace Nastavení 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 zápisu externích souborů pravidel najdete v referenčních informacích k nástroji NMAKE.

    V tomto tématu