Delen via


MFC-debugging technieken

Als u fouten in een MFC-programma opspoort, kunnen deze foutopsporingstechnieken nuttig zijn.

AfxDebugBreak

MFC biedt een speciale AfxDebugBreak-functie voor hardcoderingsonderbrekingspunten in broncode:

AfxDebugBreak( );

Op Intel-platforms produceert AfxDebugBreak de volgende code, die in broncode onderbreekt in plaats van in kernelcode.

_asm int 3

Op andere platforms roept AfxDebugBreak alleen DebugBreak aan.

Verwijder AfxDebugBreak instructies als u een release-build maakt, of gebruik #ifdef _DEBUG om de instructies te omsluiten.

De TRACE-macro

Als u berichten uit uw programma wilt weergeven in het venster Uitvoer van foutopsporingsprogramma, kunt u de ATLTRACE-macro of de MFC TRACE-macro gebruiken. Net als asserties zijn de traceringsmacro's alleen actief in de foutopsporingsversie van uw programma en verdwijnen ze wanneer ze zijn gecompileerd in de releaseversie.

In de volgende voorbeelden ziet u een aantal manieren waarop u de macro TRACE kunt gebruiken. Net als printf kan de macro TRACE een aantal argumenten verwerken.

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

De macro TRACE verwerkt op de juiste manier zowel tekens* als wchar_t* parameters. In de volgende voorbeelden ziet u het gebruik van de MACRO TRACE, samen met verschillende typen tekenreeksparameters.

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

Zie Diagnostische services voor meer informatie over de macro TRACE.

Geheugenlekken detecteren in MFC

MFC biedt klassen en functies voor het detecteren van geheugen dat is toegewezen, maar nooit de toewijzing ongedaan wordt gemaakt.

Geheugentoewijzingen bijhouden

In MFC kunt u de macro DEBUG_NEW gebruiken in plaats van de nieuwe operator om geheugenlekken te vinden. In de debug-versie van uw programma houdt DEBUG_NEW de bestandsnaam en het regelnummer bij voor elk object dat het toewijst. Wanneer u een releaseversie van uw programma compileert, DEBUG_NEW wordt deze omgezet in een eenvoudige nieuwe bewerking zonder de bestandsnaam en regelnummergegevens. U betaalt dus geen snelheidsstraf in de Release-versie van uw programma.

Als u het hele programma niet opnieuw wilt schrijven voor gebruik DEBUG_NEW in plaats van nieuw, kunt u deze macro definiëren in de bronbestanden:

#define new DEBUG_NEW

Wanneer u een objectdump uitvoert, wordt voor elk toegewezen DEBUG_NEW object het bestand en het regelnummer weergegeven waar het is toegewezen, zodat u de bronnen van geheugenlekken kunt opsporen.

De debug versie van het MFC-framework gebruikt DEBUG_NEW automatisch, maar uw code doet dat niet. Als u de voordelen van DEBUG_NEW wilt, moet u DEBUG_NEW expliciet gebruiken of #define new, zoals hierboven wordt weergegeven.

Geheugendiagnose inschakelen

Voordat u de faciliteiten voor geheugendiagnose kunt gebruiken, moet u diagnostische tracering inschakelen.

Geheugendiagnose in- of uitschakelen

  • Roep de globale functie AfxEnableMemoryTracking aan om de allocator voor diagnostisch geheugen in of uit te schakelen. Omdat geheugendiagnose standaard is ingeschakeld in de foutopsporingsbibliotheek, gebruikt u deze functie doorgaans om ze tijdelijk uit te schakelen, waardoor de uitvoeringssnelheid van het programma wordt verhoogd en de diagnostische uitvoer wordt verminderd.

    Specifieke diagnostische functies voor geheugen selecteren met afxMemDF

  • Als u meer controle wilt over de diagnostische functies van het geheugen, kunt u selectief diagnostische functies voor geheugen in- en uitschakelen door de waarde van de globale MFC-variabele afxMemDF in te stellen. Deze variabele kan de volgende waarden hebben, zoals opgegeven door het opgesomde type afxMemDF.

    Waarde Beschrijving
    allocMemDF Schakel de diagnostische geheugenallocator in (standaard).
    delayFreeMemDF Vertraging bij het vrijmaken van geheugen bij het aanroepen delete of free totdat het programma wordt afgesloten. Dit zorgt ervoor dat uw programma de maximale hoeveelheid geheugen toewijst.
    checkAlwaysMemDF Roep AfxCheckMemory aan telkens wanneer geheugen wordt toegewezen of vrijgemaakt.

    Deze waarden kunnen in combinatie worden gebruikt door een logische OF-bewerking uit te voeren, zoals hier wordt weergegeven:

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

Momentopnamen van geheugen maken

  1. Maak een CMemoryState-object en roep de CMemoryState::Checkpoint-lidfunctie aan. Hiermee maakt u de eerste momentopname van het geheugen.

  2. Nadat uw programma de geheugentoewijzings- en deallocatiebewerkingen heeft uitgevoerd, maakt u een ander CMemoryState object en roept Checkpoint u dat object aan. Hiermee krijgt u een tweede momentopname van het geheugengebruik.

  3. Maak een derde CMemoryState object en roep de CMemoryState::Difference lidfunctie aan, waarbij de twee vorige CMemoryState objecten als argumenten worden aangegeven. Als er een verschil is tussen de twee geheugenstatussen, retourneert de Difference functie een niet-nulwaarde. Dit geeft aan dat sommige geheugenblokken niet zijn toegewezen.

    In dit voorbeeld ziet u hoe de code eruitziet:

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

    U ziet dat de instructies voor geheugencontrole tussen haakjes worden geplaatst door #ifdef _DEBUG/#endif blokken, zodat ze alleen worden gecompileerd in foutopsporingsversies van uw programma.

    Nu u weet dat er een geheugenlek bestaat, kunt u een andere lidfunctie gebruiken, CMemoryState::D umpStatistics waarmee u deze kunt vinden.

Geheugenstatistieken weergeven

De functie CMemoryState::D deductie kijkt naar twee geheugenstatusobjecten en detecteert objecten die niet zijn toegewezen vanaf de heap tussen de begin- en eindstatussen. Nadat u geheugenmomentopnamen hebt gemaakt en deze hebt vergeleken met behulp CMemoryState::Differencevan, kunt u CMemoryState::D umpStatistics aanroepen om informatie op te halen over de objecten die niet zijn toegewezen.

Bekijk het volgende voorbeeld:

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

Een voorbeelddump uit het voorbeeld ziet er als volgt uit:

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

Vrije blokken zijn blokken waarvan de deallocatie is vertraagd als afxMemDF deze is ingesteld op delayFreeMemDF.

Gewone objectblokken, weergegeven op de tweede regel, blijven toegewezen aan de heap.

Niet-objectblokken omvatten matrices en structuren die zijn toegewezen met new. In dit geval zijn er vier niet-objectblokken toegewezen aan de heap, maar niet ongedaan gemaakt.

Largest number used geeft het maximale geheugen dat door het programma op elk gewenst moment wordt gebruikt.

Total allocations geeft de totale hoeveelheid geheugen die door het programma wordt gebruikt.

Objectdumps nemen

In een MFC-programma kunt u CMemoryState::D umpAllObjectsSince gebruiken om een beschrijving te dumpen van alle objecten op de heap die niet zijn toegewezen. DumpAllObjectsSince dumpt alle objecten die zijn toegewezen sinds de laatste CMemoryState::Checkpoint. Als er geen Checkpoint aanroep heeft plaatsgevonden, DumpAllObjectsSince dumpt u alle objecten en niet-objecten die zich momenteel in het geheugen bevinden.

Opmerking

Voordat u MFC-objectdump kunt gebruiken, moet u diagnostische tracering inschakelen.

Opmerking

MFC dumpt automatisch alle gelekte objecten wanneer uw programma wordt afgesloten, zodat u op dat moment geen code hoeft te maken om objecten te dumpen.

De volgende code voert een test uit voor een geheugenlek door het vergelijken van twee geheugenstatussen en schrijft alle objecten weg indien er een lek wordt gedetecteerd.

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

De inhoud van de dump ziet er als volgt uit:

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

De getallen in accolades aan het begin van de meeste regels geven de volgorde op waarin de objecten zijn toegewezen. Het meest recent toegewezen object heeft het hoogste aantal en wordt boven aan de dump weergegeven.

Als u de maximale hoeveelheid informatie uit een objectdump wilt halen, kunt u de Dump lidfunctie van elk CObject-afgeleide object overschrijven om de objectdump aan te passen.

U kunt een onderbrekingspunt voor een bepaalde geheugentoewijzing instellen door de globale variabele _afxBreakAlloc in te stellen op het getal dat wordt weergegeven in de accolades. Als u het programma opnieuw uitvoert, stopt de debugger de uitvoering wanneer die toewijzing plaatsvindt. Vervolgens kunt u de aanroepstack bekijken om te zien hoe uw programma tot dat punt is gekomen.

De C-runtimebibliotheek heeft een vergelijkbare functie, _CrtSetBreakAlloc, die u kunt gebruiken voor C-runtimetoewijzingen.

Geheugendumps interpreteren

Bekijk deze objectdump in meer detail:

{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

Het programma dat deze dump heeft gegenereerd, had slechts twee expliciete toewijzingen: één op de stack en één op de heap:

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

De CPerson constructor neemt drie argumenten die wijzen naar char, en die worden gebruikt om CString lidvariabelen te initialiseren. In de geheugendump ziet u het CPerson object samen met drie niet-objectblokken (3, 4 en 5). Deze bevatten de tekens voor de CString lidvariabelen en worden niet verwijderd wanneer de CPerson objectdestructor wordt aangeroepen.

Bloknummer 2 is het CPerson object zelf. $51A4 vertegenwoordigt het adres van het blok en wordt gevolgd door de inhoud van het object, die werd uitgegeven door CPerson::Dump toen het werd aangeroepen door DumpAllObjectsSince.

U kunt raden dat bloknummer 1 is gekoppeld aan de CString framevariabele vanwege het volgnummer en de grootte, die overeenkomt met het aantal tekens in de framevariabele CString . Variabelen die aan het frame zijn toegewezen, worden automatisch ongedaan gemaakt wanneer het frame buiten het bereik valt.

Framevariabelen

Over het algemeen hoeft u zich geen zorgen te maken over heap-objecten die zijn gekoppeld aan framevariabelen, omdat ze automatisch worden vrijgegeven wanneer de framevariabelen buiten de scope vallen. Om rommel in uw geheugendiagnosedumps te voorkomen, moet u de aanroepen zodanig Checkpoint plaatsen dat ze buiten het bereik van framevariabelen vallen. Plaats bijvoorbeeld bereikhaken rond de vorige toewijzingscode, zoals hier wordt weergegeven:

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

Nu de scope-haken zijn geplaatst, is de geheugendump voor dit voorbeeld als volgt:

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

Niet-objecttoewijzingen

U ziet dat sommige toewijzingen objecten zijn (zoals CPerson) en sommige niet-objecttoewijzingen zijn. "Niet-objecttoewijzingen" zijn toewijzingen voor objecten die niet zijn afgeleid van CObject of toewijzingen van primitieve C-typen zoals char, intof long. Als de klasse die afgeleid is van CObject extra ruimte toewijst, zoals voor interne buffers, zullen die objecten zowel object- als niet-objecttoewijzingen vertonen.

Geheugenlekken voorkomen

In de bovenstaande code ziet u dat het geheugenblok dat is gekoppeld aan de CString framevariabele automatisch is opgeheven en niet wordt weergegeven als geheugenlek. De automatische deallocatie die is gekoppeld aan bereikregels zorgt voor de meeste geheugenlekken die zijn gekoppeld aan framevariabelen.

Voor objecten die zijn toegewezen aan de heap, moet u het object echter expliciet verwijderen om een geheugenlek te voorkomen. Als u het laatste geheugenlek in het vorige voorbeeld wilt opschonen, verwijdert u als volgt het CPerson object dat is toegewezen aan de heap:

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

Objectdumps aanpassen

Wanneer u een klasse afleidt van CObject, kunt u de Dump lidfunctie overschrijven om aanvullende informatie te verstrekken wanneer u DumpAllObjectsSince gebruikt om objecten naar het uitvoervenster te dumpen.

De Dump functie schrijft een tekstuele weergave van de lidvariabelen van het object naar een dumpcontext (CDumpContext). De dumpcontext is vergelijkbaar met een I/O-stream. U kunt de append-operator (<<) gebruiken om gegevens naar een CDumpContext te verzenden.

Wanneer u de Dump functie overschrijft, moet u eerst de versie van de basisklasse aanroepen van Dump om de inhoud van het basisklasseobject te dumpen. Voer vervolgens een tekstuele beschrijving en waarde uit voor elke lidvariabele van uw afgeleide klasse.

De declaratie van de Dump functie ziet er als volgt uit:

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

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

Omdat objectdump alleen zinvol is wanneer u fouten in uw programma opspoort, wordt de declaratie van de Dump functie tussen een #ifdef _DEBUG/#endif blok geplaatst.

In het volgende voorbeeld roept de Dump functie eerst de functie aan voor de Dump basisklasse. Vervolgens wordt een korte beschrijving van elke lidvariabele geschreven, samen met de waarde van het lid naar de diagnostische stroom.

#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

U moet een CDumpContext argument opgeven om aan te geven waar de dumpuitvoer naartoe gaat. De foutopsporingsversie van MFC levert een vooraf gedefinieerd CDumpContext object met de naam afxDump dat uitvoer naar het foutopsporingsprogramma verzendt.

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

De grootte van een MFC-foutopsporingsbuild verkleinen

De foutopsporingsinformatie voor een grote MFC-toepassing kan veel schijfruimte in beslag nemen. U kunt een van deze procedures gebruiken om de grootte te verkleinen:

  1. Bouw de MFC-bibliotheken opnieuw met behulp van de optie /Z7, /Zi, /ZI (Debug Information Format) in plaats van /Z7. Met deze opties wordt een PDB-bestand (Single Program Database) gebouwd dat foutopsporingsinformatie voor de hele bibliotheek bevat, waardoor redundantie wordt verminderd en ruimte wordt bespaard.

  2. Bouw de MFC-bibliotheken opnieuw zonder foutopsporingsgegevens (geen optie /Z7, /Zi, /ZI (Debug Information Format). In dit geval voorkomt het ontbreken van foutopsporingsgegevens dat u de meeste foutopsporingsprogrammafaciliteiten in de MFC-bibliotheekcode kunt gebruiken, maar omdat de MFC-bibliotheken al grondig zijn opgespoord, is dit mogelijk geen probleem.

  3. Bouw uw eigen toepassing met foutopsporingsgegevens voor geselecteerde modules, zoals hieronder wordt beschreven.

Een MFC-app bouwen met foutopsporingsinformatie voor geselecteerde modules

Door geselecteerde modules te bouwen met de MFC-foutopsporingsbibliotheken kunt u stapstappen en de andere foutopsporingsfaciliteiten in deze modules gebruiken. Deze procedure maakt gebruik van zowel de foutopsporings- als releaseconfiguraties van het project, waardoor de wijzigingen die in de volgende stappen worden beschreven, noodzakelijk zijn (en ook een 'alles opnieuw opbouwen' vereist wanneer een volledige release-build vereist is).

  1. Selecteer het project in Solution Explorer.

  2. Selecteer eigenschapspagina's in het menu Beeld.

  3. Eerst maakt u een nieuwe projectconfiguratie.

    1. Klik in het <dialoogvenster Projecteigenschappenpagina's> op de knop Configuration Manager.

    2. Zoek in het dialoogvenster Configuration Manager uw project in het raster. Selecteer in de kolom <.

    3. Typ in het dialoogvenster Nieuwe projectconfiguratie een naam voor de nieuwe configuratie, zoals Gedeeltelijke foutopsporing, in het vak Naam van projectconfiguratie .

    4. Kies Release in de lijst Instellingen kopiëren.

    5. Klik op OK om het dialoogvenster Nieuwe projectconfiguratie te sluiten.

    6. Sluit het dialoogvenster Configuration Manager .

  4. Nu stelt u opties in voor het hele project.

    1. Selecteer in het dialoogvenster Eigenschappenpagina's onder de map Configuratie-eigenschappen de categorie Algemeen .

    2. Vouw in het raster met projectinstellingen projectstandaarden uit (indien nodig).

    3. Zoek onder Project Defaults het gebruik van MFC. De huidige instelling wordt weergegeven in de rechterkolom van het raster. Klik op de huidige instelling en wijzig in MFC in een statische bibliotheek gebruiken.

    4. Open in het linkerdeelvenster van het dialoogvenster Eigenschappenpagina's de map C/C++ en selecteer Preprocessor. Zoek in het eigenschappenraster preprocessordefinities en vervang 'NDEBUG' door '_DEBUG'.

    5. Open in het linkerdeelvenster van het dialoogvenster Eigenschappenpagina's de map Linker en selecteer de invoercategorie . Zoek in het eigenschappenraster aanvullende afhankelijkheden. Typ in de instelling Aanvullende afhankelijkheden "NAFXCWD.LIB" en "LIBCMT".

    6. Klik op OK om de nieuwe buildopties op te slaan en het dialoogvenster Eigenschappenpagina's te sluiten.

  5. Selecteer Opnieuw opbouwen in het menu Opbouwen. Hiermee verwijdert u alle foutopsporingsgegevens uit uw modules, maar heeft dit geen invloed op de MFC-bibliotheek.

  6. Nu moet u foutopsporingsgegevens weer toevoegen aan geselecteerde modules in uw toepassing. Houd er rekening mee dat u onderbrekingspunten kunt instellen en andere foutopsporingsprogrammafuncties alleen kunt uitvoeren in modules die u hebt gecompileerd met foutopsporingsgegevens. Voer de volgende stappen uit voor elk projectbestand waarin u foutopsporingsgegevens wilt opnemen:

    1. Open in Solution Explorer de map Bronbestanden die zich onder uw project bevindt.

    2. Selecteer het bestand waarvoor u foutopsporingsgegevens wilt instellen.

    3. Selecteer eigenschapspagina's in het menu Beeld.

    4. Open in het dialoogvenster Eigenschappenpagina's onder de map Configuratie-instellingen de map C/C++ en selecteer vervolgens de categorie Algemeen .

    5. Zoek in het eigenschappenraster naar Debug Information Format.

    6. Klik op de instellingen voor foutopsporingsgegevensindeling en selecteer de gewenste optie (meestal /ZI) voor foutopsporingsgegevens.

    7. Als u een toepassingswizard gebruikt of vooraf gecompileerde headers hebt, moet u de vooraf gecompileerde headers uitschakelen of deze opnieuw compileren voordat u de andere modules gaat compileren. Anders ontvangt u de waarschuwing C4650 en het foutbericht C2855. U kunt vooraf gecompileerde headers uitschakelen door de instelling Vooraf gecompileerde headers maken/gebruiken te wijzigen in het <dialoogvenster Projecteigenschappen> (map Configuratie-eigenschappen, C/C++-submap, categorie Vooraf gecompileerde headers).

  7. Selecteer Build in het menu Opbouwen om projectbestanden te herbouwen die verouderd zijn.

    Als alternatief voor de techniek die in dit onderwerp wordt beschreven, kunt u een extern makefile gebruiken om afzonderlijke opties voor elk bestand te definiëren. Als u in dat geval een koppeling wilt maken met de MFC-foutopsporingsbibliotheken, moet u de _DEBUG vlag voor elke module definiëren. Als u MFC-releasebibliotheken wilt gebruiken, moet u NDEBUG definiëren. Zie de NMAKE-verwijzing voor meer informatie over het schrijven van externe makefiles.