Techniky ladění MFC
Pokud ladíte program MFC, mohou být tyto techniky ladění užitečné.
V tomto tématu
Detekce nevrácené paměti v mfc
AfxDebugBreak
KNIHOVNA MFC poskytuje speciální funkci AfxDebugBreak pro pevné kódování zarážek ve zdrojovém kódu:
AfxDebugBreak( );
Na platformách Intel vytvoří následující kód, který AfxDebugBreak se přeruší ve zdrojovém kódu, a ne ve zdrojovém kódu:
_asm int 3
Na jiných platformách AfxDebugBreak se pouze volá DebugBreak .
Nezapomeňte odebrat příkazy při vytváření sestavení verze nebo je ohraničovat AfxDebugBreak #ifdef _DEBUG pomocí .
Makro TRACE
K zobrazení zpráv z programu v okně Výstup ladicího programumůžete použít makro ATLTRACE nebo makro MFC TRACE. Stejně jako kontrolní výrazyjsou makra trasování aktivní pouze v ladicí verzi programu a zmizí při kompilaci ve verzi release.
Následující příklady ukazují některé způsoby použití makra TRACE. Podobně printf jako může makro TRACE 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 typu char * * wchar_t. Následující příklady ukazují použití makra TRACE společně s různými typy parametrů řetězce.
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 makru TRACE najdete v tématu Diagnostic Services.
Detekce nevrácené paměti v MFC
KNIHOVNA MFC poskytuje třídy a funkce pro detekci paměti, která je přidělena, ale nikdy neudělována.
Sledování přidělení paměti
V knihovně MFC lze použít DEBUG_NEW místo nového operátoru k vyhledání nevrácené paměti. V ladicí verzi programu sleduje název souboru a číslo řádku pro každý objekt, DEBUG_NEW který přidělí. Při kompilaci verze programu se nástroj překládá na jednoduchou novou operaci bez názvu souboru DEBUG_NEW a informací o čísle řádku. Proto neplatíte žádné omezení rychlosti ve verzi vašeho programu.
Pokud nechcete přepsat celý program tak, aby se místo nového , můžete toto makro definovat DEBUG_NEW ve zdrojových souborech:
#define new DEBUG_NEW
Když použijete výpisobjektu , každý objekt přidělený pomocí zobrazí číslo souboru a řádku, kam byl přidělen, což vám umožní určit zdroje nevrácené DEBUG_NEW paměti.
Ladicí verze rozhraní MFC používá DEBUG_NEW automaticky, ale váš kód ne. Pokud chcete výhody funkce , musíte použít explicitně nebo DEBUG_NEW DEBUG_NEW #define nový, jak je znázorněno výše.
Povolení diagnostiky paměti
Než budete moci používat diagnostická zařízení paměti, musíte povolit diagnostické trasování.
Povolení nebo zakázání diagnostiky paměti
Voláním globální funkce AfxEnableMemoryTracking povolte nebo zakažte alokátor diagnostické paměti. Vzhledem k tomu, že diagnostika paměti je v knihovně ladění ve výchozím nastavení povolená, použijete tuto funkci k jejich dočasnému vypnutí, což zvyšuje rychlost provádění programu a snižuje výstup diagnostiky.
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é funkce diagnostiky paměti nastavením hodnoty globální proměnné MFC afxMemDF. Tato proměnná může mít následující hodnoty určené výčtem typu afxMemDF.
Hodnota Popis allocMemDF Zapněte alokátor diagnostické paměti (výchozí). delayFreeMemDF Zpoždění při uvolnění paměti při deletevolání nebo do ukončenífreeprogramu. To způsobí, že program přidělí maximální možnou velikost paměti.checkAlwaysMemDF Volejte AfxCheckMemory pokaždé, když je paměť přidělena nebo volná. 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
Vytvořte objekt CMemoryState a zavolejte členská funkce CMemoryState::Checkpoint. Tím se vytvoří první snímek paměti.
Jakmile program provede operace přidělení a přidělení paměti, vytvořte další objekt a
CMemoryStatezavolejteCheckpointpro tento objekt. Tím se získá druhý snímek využití paměti.Vytvořte třetí objekt a
CMemoryStatezavolejte jeho CMemoryState::D ifference členské funkce, která dodá jako argumenty dva předchozíCMemoryStateobjekty. Pokud je mezi těmito dvěma stavy paměti rozdíl,Differencevrátí funkce nenulovou hodnotu. To znamená, že některé bloky paměti nebyly přiděleny.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" ); } #endifVšimněte si, že příkazy kontroly paměti jsou v závorkách #ifdef _DEBUG /#endif bloky tak, aby byly kompilovány pouze v ladicích verzích programu.
Teď, když víte, že nevrácená paměť existuje, můžete použít jinou členickou funkci CMemoryState::D umpStatistics, která vám ji pomůže najít.
Zobrazení statistik paměti
Funkce CMemoryState::D ifference vyhledá dva objekty stavu paměti a detekuje všechny objekty, které nejsou z haldy mezi počátečním a koncovým stavem přiděleny. Po pořízení snímků paměti a jejich porovnání pomocí můžete volat CMemoryState::Difference CMemoryState::D umpStatistics a získat informace o objektech, které nebyly přiděleny.
Uvažujte 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á takhle:
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ž navracení je zpožděné, afxMemDF pokud bylo nastaveno na delayFreeMemDF .
Běžné bloky objektů zobrazené na druhém řádku zůstanou přidělené na haldě.
Jiné než objektové bloky zahrnují pole a struktury přidělené pomocí new . V tomto případě byly na haldě přiděleny čtyři ne object bloky, které však nebyly přidělením.
Largest number used poskytuje maximální paměť, kterou program může kdykoli použít.
Total allocations poskytuje celkovou velikost paměti využínou programem.
Převzetí výpisů paměti objektů
V programu MFC můžete použít CMemoryState::D umpAllObjectsSince k výpisu popisu všech objektů na haldě, které nebyly přiděleny. DumpAllObjectsSince vy výpisy všech objektů přidělených od posledního objektu CMemoryState::Checkpoint. Pokud Checkpoint neprošly žádné volání, vypište všechny objekty a objekty, které nejsou aktuálně DumpAllObjectsSince v paměti.
Poznámka
Než budete moci použít ukládání objektů MFC, je nutné povolit diagnostické trasování.
Poznámka
Mfc automaticky vykreslí všechny prozrazené objekty při ukončení programu, takže v tomto okamžiku není nutné vytvářet kód pro výpis objektů.
Následující kód testuje nevrácenou paměť porovnáním dvou stavů paměti a vybaví všechny objekty, pokud je zjištěn nevrácená paměť.
if( diffMemState.Difference( oldMemState, newMemState ) )
{
TRACE( "Memory leaked!\n" );
diffMemState.DumpAllObjectsSince();
}
Obsah výpisu paměti vypadá takhle:
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. Naposledy přidělený objekt má nejvyšší číslo a zobrazí se v horní části výpisu.
Chcete-li získat maximální množství informací z výpisu objektu, můžete přepsat členská funkce Dump CObject libovolného objektu odvozeného 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é na číslo zobrazené ve složených _afxBreakAlloc závorkách. Pokud program znovu spustíte, ladicí program při tomto přidělení přeruší provádění. Pak se můžete podívat na zásobník volání a zjistit, jak se váš program do tohoto bodu dostal.
Knihovna běhů 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
Podívejte se na tento výpis objektu podrobněji:
{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 na 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 ukazateli na , které char se používají k inicializaci členských CString proměnných. V výpisu paměti vidíte objekt spolu se třemi neobjektovými CPerson bloky (3, 4 a 5). Tyto znaky jsou pro členské proměnné a nebudou odstraněny při vyvolání CString CPerson destruktoru objektu.
Blok číslo 2 je CPerson samotný objekt. $51A4 představuje adresu bloku a za ním následuje obsah objektu , který byl výstupem :: při volání funkce CPerson Dump DumpAllObjectsSince.
Můžete odhadnout, že blok číslo 1 je přidružen k proměnné rámce z důvodu pořadového čísla a velikosti, které odpovídají počtu znaků v CString proměnné CString rámce. Proměnné přidělené v rámci se automaticky udělují, když rámec přejde mimo rozsah.
Proměnné rámce
Obecně platí, že byste se neměli starat o objekty haldy přidružené k proměnným rámců, protože jsou automaticky přiděleny, když proměnné rámce přechádují rozsah. Abyste se vyhnuli nepotř počtům v diagnostických výpisech paměti, měli byste volání umístit tak, aby byla mimo rozsah Checkpoint proměnných rámce. Například umístěte závorky rozsahu kolem předchozího kódu přidělení, jak je znázorněno zde:
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 objektu
Všimněte si, že některá přidělení jsou objekty (například ) a CPerson některá jsou jiná než přidělení objektů. "Přidělení bez objektu" jsou přidělení pro objekty, které nejsou odvozeny z nebo přidělení primitivních typů jazyka C, jako je CObject char , nebo int long . Pokud odvozená třída CObjectpřiděluje další místo, například pro interní vyrovnávací paměti, tyto objekty budou zobrazovat přidělení objektů i objektů bez objektu.
Prevence nevrácené paměti
Ve výše uvedeném kódu si všimněte, že blok paměti přidružený k proměnné rámce byl automaticky přidělen a neukašl se jako CString nevrácená paměť. Automatické přidělení přidružené k pravidlům oborů se postará o většinu nevrácených paměti spojených s proměnnými rámce.
Pro objekty přidělené na haldě je však nutné objekt explicitně odstranit, aby se zabránilo nevrácení paměti. Pokud chcete vyčistit poslední nevrácenou paměť v předchozím příkladu, odstraňte objekt CPerson přidělený na 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ů paměti objektů
Když odvodíte třídu z objektu CObject,můžete přepsat členská funkce tak, aby poskytovala další informace, když použijete Dump DumpAllObjectsSince k výpisu objektů do okna Výstup.
Funkce zapíše textovou reprezentaci členských proměnných objektu do kontextu Dump výpisu paměti (CDumpContext). Kontext výpisu paměti je podobný V/V datovému proudu. K odeslání dat do můžete použít operátor připojení ( << CDumpContext ).
Při přepsání funkce byste měli nejprve zavolat verzi základní třídy pro výpis obsahu Dump Dump objektu základní třídy. Výstupem pak bude textový popis a hodnota pro každou člennou proměnnou odvozené třídy.
Deklarace funkce Dump vypadá takhle:
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 při ladění programu dává smysl vysunutí objektů, je deklarace funkce v závorkách s blokem Dump #ifdef _DEBUG/#endif bloku.
V následujícím příkladu funkce Dump nejprve volá Dump funkci pro svou základní třídu. Potom zapíše krátký popis každé členské proměnné spolu s hodnotou členu do diagnostického datového proudu.
#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
Musíte zadat CDumpContext argument, který určí, kam se má výstup výpisu paměti dostat. Ladicí verze knihovny MFC poskytuje předdefinovaný CDumpContext objekt s názvem , který odesílá výstup do afxDump 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í ladění MFC
Informace o ladění pro velkou aplikaci MFC mohou za trvat hodně místa na disku. Velikost můžete zmenšit jedním z těchto postupů:
Znovu sestavte knihovny MFC pomocí možnosti /Z7, /Zi, /ZI (formát informací o ladění) místo /Z7. Tyto možnosti sestaví soubor databáze s jedním programem (PDB), který obsahuje informace o ladění pro celou knihovnu, čímž se sníží redundance a ušetří místo.
Znovu sestavte knihovny MFC bez informací o ladění (bez možnosti /Z7, /Zi, /ZI (formát informací o ladění). V takovém případě vám absence ladicích informací zabrání v používání většiny ladicích nástrojů v kódu knihovny MFC, ale vzhledem k tomu, že knihovny MFC jsou již důkladně laděné, nemusí to být problém.
Sestavte si vlastní aplikaci s informacemi o ladění pro vybrané moduly, jak je popsáno níže.
Sestavení aplikace MFC s ladicími informacemi pro vybrané moduly
Sestavení vybraných modulů pomocí ladicích knihoven MFC umožňuje používat krokování a další možnosti ladění v těchto modulech. Tento postup využívá konfiguraci ladění i vydání projektu, takže vyžaduje změny popsané v následujících krocích (a také vytvoření "opětovného sestavení vše", když se vyžaduje úplné sestavení verze).
V Průzkumník řešení vyberte projekt.
V nabídce View (Zobrazení) vyberte Property Pages (Stránky vlastností).
Nejprve vytvoříte novou konfiguraci projektu.
V dialogovém <Project> okně Stránky vlastností klikněte na tlačítko Správce konfigurace vlastností.
V dialogovém Správce konfigurace vyhledejteprojekt v mřížce. Ve sloupci Konfigurace vyberte <New...> .
V dialogovém Project Novákonfigurace zadejte název nové konfigurace, například Částečné ladění, do pole Project konfigurace.
V seznamu Nastavení z zvolte Verze.
Kliknutím na OK zavřete dialogové okno Project nové konfigurace.
Zavřete Správce konfigurace dialogové okno.
Teď nastavíte možnosti pro celý projekt.
V dialogovém okně Stránky vlastností vyberte ve složce Vlastnosti konfigurace kategorii Obecné.
V mřížce nastavení projektu rozbalte Project Výchozí hodnoty (pokud je to potřeba).
V Project výchozí hodnoty najděte možnost 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žít MFC ve statické knihovně.
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" textem "_DEBUG".
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".
Kliknutím na OK uložte nové možnosti sestavení a zavřete dialogové okno Stránky vlastností.
V nabídce Build (Sestavení) vyberte Rebuild (Znovu sestavit). To odebere všechny informace o ladění z modulů, ale nemá vliv na knihovnu MFC.
Teď je nutné přidat informace o ladění zpět do vybraných modulů ve vaší aplikaci. Mějte na paměti, že zarážky můžete nastavit a provádět další funkce ladicího programu pouze v modulech, které jste zkompilili s informacemi o ladění. Pro každý soubor projektu, do kterého chcete zahrnout informace o ladění, proveďte následující kroky:
V Průzkumník řešení otevřete složku Zdrojové soubory umístěnou ve vašem projektu.
Vyberte soubor, pro který chcete nastavit informace o ladění.
V nabídce View (Zobrazení) vyberte Property Pages (Stránky vlastností).
V dialogovém okně Stránky vlastností ve složce Konfigurace Nastavení otevřete složku C/C++ a pak vyberte kategorii Obecné.
V mřížce vlastností najděte Formát informací o ladění.
Klikněte na nastavení Formát informací o ladění a vyberte požadovanou možnost (obvykle /ZI) pro informace o ladění.
Pokud používáte aplikaci vygenerovanou průvodcem aplikace nebo máte předkompilované hlavičky, musíte předkompilované hlavičky vypnout nebo je znovu zkompilovat před kompilací ostatních modulů. Jinak 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 (složka Vlastnosti konfigurace, podsložka C/C++, kategorie Předkompilované <Project> hlavičky).
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 pro propojení s ladicími knihovnami MFC nutné definovat příznak _DEBUG pro každý modul. 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.