Dela via


Felsökningstekniker för MFC

Om du felsöker ett MFC-program kan dessa felsökningstekniker vara användbara.

AfxDebugBreak (på engelska)

MFC tillhandahåller en speciell AfxDebugBreak-funktion för hårdkodande brytpunkter i källkoden:

AfxDebugBreak( );

På Intel-plattformar genereras följande kod, som bryter i källkoden i stället för kärnkoden:

_asm int 3

På andra plattformar AfxDebugBreak bara anropar DebugBreak.

Se till att ta bort AfxDebugBreak satser när du skapar en släppversion eller använder #ifdef _DEBUG för att omge dem.

TRACE-makro

Om du vill visa meddelanden från programmet i felsökningsprogrammets utdatafönster kan du använda ATLTRACE-makrot eller MFC TRACE-makrot . Precis som uttryck är spårningsmakronen endast aktiva i felsökningsversionen av programmet och försvinner när de kompileras i släppversionen.

I följande exempel visas några av de sätt som du kan använda TRACE-makrot på. Precis som printfkan TRACE-makrot hantera ett antal 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 );

TRACE-makrot hanterar parametrarna char* och wchar_t* korrekt. I följande exempel visas användningen av TRACE-makrot tillsammans med olika typer av strängparametrar.

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

Mer information om TRACE-makrot finns i Diagnostiktjänster.

Identifiera minnesläckor i MFC

MFC tillhandahåller klasser och funktioner för att identifiera minne som allokeras men aldrig frigörs.

Spåra minnesallokeringar

I MFC kan du använda makrot DEBUG_NEW i stället för den nya operatorn för att hitta minnesläckor. I felsökningsversionen av programmet DEBUG_NEW håller du reda på filnamnet och radnumret för varje objekt som det allokerar. När du kompilerar en versionsversion av ditt program DEBUG_NEW , matchas till en enkel ny åtgärd utan filnamn och radnummerinformation. Därför betalar du ingen hastighetsstraff i versionen av ditt program.

Om du inte vill skriva om hela programmet så att det används DEBUG_NEW i stället för nytt kan du definiera det här makrot i källfilerna:

#define new DEBUG_NEW

När du gör en objektdumpning visar varje objekt som allokerats med DEBUG_NEW filen och radnumret där det allokerades, så att du kan hitta källorna till minnesläckor.

Felsökningsversionen av MFC-ramverket använder DEBUG_NEW automatiskt, men din kod gör det inte. Om du vill ha fördelarna med DEBUG_NEWmåste du använda DEBUG_NEW explicit eller #define nytt enligt ovan.

Aktivera minnesdiagnostik

Innan du kan använda minnesdiagnostikanläggningarna måste du aktivera diagnostisk spårning.

Aktivera eller inaktivera minnesdiagnostik

  • Anropa den globala funktionen AfxEnableMemoryTracking för att aktivera eller inaktivera diagnostikminnesallokeraren. Eftersom minnesdiagnostik är aktiverat som standard i felsökningsbiblioteket använder du vanligtvis den här funktionen för att tillfälligt inaktivera dem, vilket ökar programmets körningshastighet och minskar diagnostikutdata.

    Så här väljer du specifika minnesdiagnostikfunktioner med afxMemDF

  • Om du vill ha mer exakt kontroll över minnesdiagnostikfunktionerna kan du selektivt aktivera och inaktivera enskilda minnesdiagnostikfunktioner genom att ange värdet för den globala MFC-variabeln afxMemDF. Den här variabeln kan ha följande värden som anges av den uppräknade typen afxMemDF.

    Värde Beskrivning
    allocMemDF Aktivera diagnostikminnesallokering (standard).
    delayFreeMemDF Fördröj frigöring av minne när du anropar delete eller free tills programmet avslutas. Detta gör att programmet allokerar maximalt minne.
    checkAlwaysMemDF Anropa AfxCheckMemory varje gång minnet allokeras eller frigörs.

    Dessa värden kan användas i kombination genom att utföra en logisk-OR-åtgärd, som du ser här:

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

Ta minnesögonblicksbilder

  1. Skapa ett CMemoryState-objekt och anropa medlemsfunktionen CMemoryState::Checkpoint . Detta skapar den första minnesögonblicksbilden.

  2. När programmet har slutfört sina minnesallokerings- och frigöringsåtgärder skapar du ett annat CMemoryState objekt och anropar Checkpoint för det objektet. Detta hämtar en andra ögonblicksbild av minnesanvändningen.

  3. Skapa ett tredje CMemoryState objekt och anropa dess CMemoryState::D ifference-medlemsfunktion och ange som argument de två föregående CMemoryState objekten. Om det finns en skillnad mellan de två minnestillstånden Difference returnerar funktionen ett värde som inte är noll. Detta indikerar att vissa minnesblock inte har frigjorts.

    Det här exemplet visar hur koden ser ut:

    // 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
    

    Observera att minneskontrollinstruktionerna är hakparenteserade med #ifdef _DEBUG/#endif block så att de endast kompileras i felsökningsversioner av programmet.

    Nu när du vet att det finns en minnesläcka kan du använda en annan medlemsfunktion, CMemoryState::D umpStatistics som hjälper dig att hitta den.

Visa statistiken för minne

Funktionen CMemoryState::Difference tittar på två minnestillståndsobjekt och identifierar objekt som inte har frigjorts från heapminnet mellan starttillståndet och sluttillståndet. När du har tagit minnesögonblicksbilder och jämfört dem med CMemoryState::Difference, kan du anropa CMemoryState::DumpStatistics för att få information om de objekt som inte har frigjorts.

Tänk på följande exempel:

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

En exempeldump från exemplet ser ut så här:

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

Fria block är block vars frigöring fördröjs om afxMemDF har satts till delayFreeMemDF.

Vanliga objektblock, som visas på den andra raden, förblir allokerade på heapen.

Block som inte är objekt inkluderar matriser och strukturer som allokerats med new. I det här fallet allokerades fyra icke-objektblock på heapen men frigjordes inte.

Largest number used ger maximalt minne som används av programmet när som helst.

Total allocations ger den totala mängden minne som används av programmet.

Skapa objektdumpar

I ett MFC-program kan du använda CMemoryState::DumpAllObjectsSince för att generera en beskrivning av alla objekt på högen som inte har frigjorts. DumpAllObjectsSince dumpar alla objekt som allokerats sedan den senaste CMemoryState::Checkpoint. Om inget Checkpoint anrop har ägt rum DumpAllObjectsSince dumpar alla objekt och icke-objekt som för närvarande finns i minnet.

Anmärkning

Innan du kan använda MFC-objektdumpning måste du aktivera diagnostisk spårning.

Anmärkning

MFC dumpar automatiskt alla läckta objekt när programmet avslutas, så du behöver inte skapa kod för att dumpa objekt vid den tidpunkten.

Följande kod testar en minnesläcka genom att jämföra två minnestillstånd och dumpar alla objekt om en läcka identifieras.

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

Innehållet i dumpen ser ut så här:

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

Talen i klammerparenteser i början av de flesta rader anger i vilken ordning objekten allokerades. Det senast allokerade objektet har det högsta talet och visas överst på dumpen.

Om du vill få ut maximal mängd information från en objektdumpning kan du åsidosätta Dump medlemsfunktionen för alla CObject-härledda objekt för att anpassa objektdumpen.

Du kan ange en brytpunkt för en viss minnesallokering genom att ange den globala variabeln _afxBreakAlloc till det tal som visas i klammerparenteserna. Om du kör programmet igen avbryts körningen av felsökningsprogrammet när allokeringen sker. Du kan sedan titta på anropsstacken för att se hur programmet kom till den punkten.

C-körningsbiblioteket har en liknande funktion, _CrtSetBreakAlloc, som du kan använda för C-körningsallokeringar.

Tolka minnesdumpar

Titta närmare på den här objektdumpningen:

{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

Programmet som genererade den här dumpen hade bara två explicita allokeringar – en på stacken och en på heapen:

// 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" );

Konstruktorn CPerson tar tre argument som är pekare till char, som används för att initiera CString medlemsvariabler. I minnesdumpningen CPerson kan du se objektet tillsammans med tre icke-objektblock (3, 4 och 5). Dessa innehåller tecknen för CString medlemsvariablerna och tas inte bort när CPerson objektförstöraren anropas.

Blocknummer 2 är CPerson själva objektet. $51A4 representerar blockets adress och följs av innehållet i objektet, som har utgått från CPerson::Dump när det anropades av DumpAllObjectsSince.

Du kan gissa att blocknummer 1 är associerat med CString ramvariabeln på grund av dess sekvensnummer och storlek, vilket matchar antalet tecken i ramvariabeln CString . Variabler som allokeras i ramen frigörs automatiskt när ramen hamnar utanför omfånget.

Ramvariabler

I allmänhet bör du inte oroa dig för heap-objekt som är associerade med ramvariabler eftersom de frigörs automatiskt när ramvariablerna hamnar utanför omfånget. För att undvika oreda i minnesdiagnostikdumpar bör du placera dina anrop till Checkpoint så att de inte omfattas av ramvariablerna. Placera till exempel omfångsparenteser runt den tidigare allokeringskoden, som du ser här:

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();

Med omfångsparenteserna på plats är minnesdumpen för det här exemplet följande:

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

Icke-objektallokeringar

Observera att vissa allokeringar är objekt (till exempel CPerson) och vissa är icke-objektallokeringar. "Nonobject-allokeringar" är allokeringar för objekt som inte härleds från CObject eller allokeringar av primitiva C-typer som char, inteller long. Om klassen CObject-derived allokerar ytterligare utrymme, till exempel för interna buffertar, visar dessa objekt både objekt- och icke-objektallokeringar.

Förhindra minnesläckor

Observera i koden ovan att minnesblocket som är associerat med CString ramvariabeln har frigjorts automatiskt och inte visas som en minnesläcka. Den automatiska frigöring som är associerad med omfångsregler tar hand om de flesta minnesläckor som är associerade med ramvariabler.

För objekt som allokerats på heap-minnet måste du dock explicitt ta bort objektet för att förhindra minnesläckage. Om du vill rensa den senaste minnesläckan i föregående exempel, tar du bort objektet CPerson som allokerades på heap så här:

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

Anpassa objektdumpar

När du härleder en klass från CObject kan du åsidosätta Dump medlemsfunktionen för att ange ytterligare information när du använder DumpAllObjectsSince för att dumpa objekt i utdatafönstret.

Funktionen Dump skriver en textrepresentation av objektets medlemsvariabler till en dumpkontext (CDumpContext). Dumpkontexten liknar en I/O-ström. Du kan använda tilläggsoperatorn (<<) för att skicka data till en CDumpContext.

När du åsidosätter Dump funktionen bör du först anropa basklassversionen för Dump för att dumpa innehållet i basklassobjektet. Mata sedan ut en textbeskrivning och ett värde för varje medlemsvariabel i din härledda klass.

Deklarationen av Dump funktionen ser ut så här:

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

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

Eftersom objektdumpning bara är meningsfullt när du felsöker programmet är deklarationen Dump av funktionen hakparenteserad med ett #ifdef _DEBUG/#endif-block .

I följande exempel anropar Dump först Dump-funktionen för basklassen. Den skriver sedan en kort beskrivning av varje medlemsvariabel tillsammans med medlemmens värde till diagnostikströmmen.

#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

Du måste ange ett CDumpContext argument för att ange vart dumputdata ska gå. Felsökningsversionen av MFC tillhandahåller ett fördefinierat CDumpContext objekt med namnet afxDump som skickar utdata till felsökningsprogrammet.

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

Minska storleken på en MFC-felsökningsversion

Felsökningsinformationen för ett stort MFC-program kan ta upp mycket diskutrymme. Du kan använda någon av dessa procedurer för att minska storleken:

  1. Återskapa MFC-biblioteken med alternativet /Z7, /Zi, /ZI (Felsökningsinformationsformat) i stället för /Z7. De här alternativen skapar en pdb-fil (single program database) som innehåller felsökningsinformation för hela biblioteket, vilket minskar redundansen och sparar utrymme.

  2. Återskapa MFC-biblioteken utan felsökningsinformation (inget alternativ för /Z7, /Zi, /ZI (Felsökningsinformationsformat ). I det här fallet hindrar bristen på felsökningsinformation dig från att använda de flesta felsökningsanläggningar i MFC-bibliotekskoden, men eftersom MFC-biblioteken redan är grundligt nedbuggade kanske det inte är något problem.

  3. Skapa ett eget program med felsökningsinformation för valda moduler endast enligt beskrivningen nedan.

Skapa en MFC-app med felsökningsinformation för valda moduler

Genom att bygga utvalda moduler med MFC-felsökningsbiblioteken kan du använda stegvisa genomgångar och de andra felsökningsfunktionerna i dessa moduler. Den här proceduren använder både felsöknings- och versionskonfigurationerna för projektet, vilket kräver de ändringar som beskrivs i följande steg (och gör även en "återskapa allt" nödvändig när en fullständig versionsversion krävs).

  1. Välj projektet i Solution Explorer.

  2. På menyn Visa väljer du Egenskapssidor.

  3. Först skapar du en ny projektkonfiguration.

    1. Klicka på knappen < i dialogrutan Projektegenskapssidor>.

    2. Leta upp projektet i rutnätet i dialogrutan Configuration Manager. I kolumnen Konfiguration väljer du <Ny...>.

    3. I dialogrutan Ny projektkonfiguration skriver du ett namn för den nya konfigurationen, till exempel "Partiell felsökning", i rutan Projektkonfigurationsnamn .

    4. I listan Kopiera inställningar från väljer du Släpp.

    5. Klicka på OK för att stänga dialogrutan Ny projektkonfiguration .

    6. Stäng dialogrutan Configuration Manager .

  4. Nu ska du ange alternativ för hela projektet.

    1. I dialogrutan Egenskapssidor går du till mappen Konfigurationsegenskaper och väljer kategorin Allmänt .

    2. I projektinställningsrutnätet utökar du Projektstandarder (om det behövs).

    3. Under Project Defaults (Standardvärden för projekt) hittar du Användning av MFC. Den aktuella inställningen visas i den högra kolumnen i rutnätet. Klicka på den aktuella inställningen och ändra den till Använd MFC i ett statiskt bibliotek.

    4. I den vänstra rutan i dialogrutan Egenskaper sidor öppnar du mappen C/C++ och väljer Preprocessor. Leta reda på PreprocessorDefinitioner i egenskapsrutnätet och ersätt "NDEBUG" med "_DEBUG".

    5. I den vänstra rutan i dialogrutan Egenskaper sidor öppnar du mappen Linker och väljer indatakategorin . Leta reda på Ytterligare beroenden i egenskapsrutnätet. I inställningen Ytterligare beroenden skriver du "NAFXCWD. LIB" och "LIBCMT".

    6. Klicka på OK för att spara de nya byggalternativen och stäng dialogrutan Egenskapssidor .

  5. På menyn Skapa väljer du Återskapa. Detta tar bort all felsökningsinformation från dina moduler men påverkar inte MFC-biblioteket.

  6. Nu måste du lägga till felsökningsinformation tillbaka till valda moduler i ditt program. Kom ihåg att du kan ange brytpunkter och utföra andra felsökningsfunktioner endast i moduler som du har kompilerat med felsökningsinformation. Utför följande steg för varje projektfil där du vill inkludera felsökningsinformation:

    1. Öppna mappen Källfiler under projektet i Solution Explorer.

    2. Välj den fil som du vill ange felsökningsinformation för.

    3. På menyn Visa väljer du Egenskapssidor.

    4. I dialogrutan Egenskapssidor går du till mappen Konfigurationsinställningar och öppnar mappen C/C++ och väljer sedan kategorin Allmänt .

    5. Leta reda på Felsökningsinformationsformat i egenskapsrutnätet.

    6. Klicka på inställningarna för felsökningsinformationsformat och välj önskat alternativ (vanligtvis /ZI) för felsökningsinformation.

    7. Om du använder ett programguidegenererat program eller har förkompilerade rubriker måste du inaktivera de förkompilerade huvudena eller kompilera om dem innan du kompilerar de andra modulerna. Annars får du varning C4650 och felmeddelandeT C2855. Du kan inaktivera förkompilerade rubriker genom att ändra inställningen Skapa/använd förkompilerade rubriker i <dialogrutan Projektegenskaper> (mappen Konfigurationsegenskaper, C/C++-undermapp, kategorin Förkompilerade rubriker).

  7. På menyn Skapa väljer du Skapa för att återskapa projektfiler som är inaktuella.

    Som ett alternativ till den teknik som beskrivs i det här avsnittet kan du använda en extern makefile för att definiera enskilda alternativ för varje fil. Om du vill länka till MFC-felsökningsbiblioteken måste du definiera flaggan _DEBUG för varje modul. Om du vill använda MFC-versionsbibliotek måste du definiera NDEBUG. Mer information om hur du skriver externa makefiles finns i NMAKE-referensen.