Megosztás a következőn keresztül:


MFC hibakeresési technikák

Ha MFC-programot hibakeresést végez, ezek a hibakeresési technikák hasznosak lehetnek.

AfxDebugBreak

Az MFC egy speciális AfxDebugBreak függvényt biztosít a forráskódban található, kemény kódolású töréspontokhoz:

AfxDebugBreak( );

Intel-platformokon AfxDebugBreak a következő kódot állítja elő, amely a kernelkód helyett a forráskódot töri meg:

_asm int 3

Más platformokon AfxDebugBreak csak meghívja DebugBreak.

Mindenképpen távolítsa el AfxDebugBreak az utasításokat, amikor létrehoz egy kiadási buildet, vagy használja #ifdef _DEBUG őket körülvenni.

A TRACE makró

Ha a program üzeneteit szeretné megjeleníteni a hibakereső Kimeneti ablakban, használhatja az ATLTRACE makrót vagy az MFC TRACE makrót. Az állításokhoz hasonlóan a nyomkövetési makrók csak a program hibakeresési verziójában aktívak, és eltűnnek a kiadási verzióban lefordítva.

Az alábbi példák a TRACE makró használatának néhány módját mutatják be. A printf makró például számos argumentumot képes kezelni.

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

A TRACE makró megfelelően kezeli a char* és wchar_t* paramétereket is. Az alábbi példák a TRACE makró használatát mutatják be különböző sztringparaméterekkel együtt.

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

A TRACE makróval kapcsolatos további információkért lásd: Diagnostic Services.

Memóriaszivárgások észlelése az MFC-ben

Az MFC osztályokat és függvényeket biztosít a lefoglalt, de soha nem felszabadított memória észleléséhez.

Memóriafoglalások nyomon követése

Az MFC-ben az új operátor helyett a makró DEBUG_NEW segítségével megkeresheti a memóriavesztéseket. A program DEBUG_NEW hibakeresési verziójában nyomon követheti az egyes lefoglalt objektumok fájlnevét és sorszámát. Amikor lefordítja a program kiadási verzióját, a fájlnév és a sorszám információi nélkül DEBUG_NEW egyszerű új műveletre oldódik fel. Így a program kiadási verziójában nem kell sebességbüntetést fizetnie.

Ha nem szeretné átírni a teljes programját úgy, hogy DEBUG_NEW-t használjon a új helyett, definálhatja ezt a makrót a forrásfájlokban.

#define new DEBUG_NEW

Amikor objektumképet végez, minden lefoglalt DEBUG_NEW objektum megjeleníti a lefoglalt fájl és sor számát, így rögzítheti a memóriavesztések forrásait.

Az MFC-keretrendszer hibakeresési verziója automatikusan használ DEBUG_NEW , de a kód nem. Ha az előnyöket DEBUG_NEWszeretné használni, explicit módon vagy DEBUG_NEW kell használnia a fent látható módon.

Memóriadiagnosztika engedélyezése

A memóriadiagnosztikai eszközök használatához engedélyeznie kell a diagnosztikai nyomkövetést.

Memóriadiagnosztika engedélyezése vagy letiltása

  • Hívja meg az AfxEnableMemoryTracking globális függvényt a diagnosztikai memória-kiosztó engedélyezéséhez vagy letiltásához. Mivel a memóriadiagnosztika alapértelmezés szerint be van kapcsolva a hibakeresési kódtárban, általában ezzel a függvénnyel ideiglenesen kikapcsolhatja őket, ami növeli a program végrehajtási sebességét, és csökkenti a diagnosztikai kimenetet.

    Adott memóriadiagnosztikai funkciók kiválasztása az afxMemDF használatával

  • Ha pontosabban szeretné szabályozni a memóriadiagnosztikai funkciókat, az AfxMemDF MFC globális változó értékének beállításával szelektíven kapcsolhatja be és ki az egyes memóriadiagnosztikai funkciókat. Ez a változó az alábbi értékekkel rendelkezhet az afxMemDF számbavételi típus által megadott módon.

    Érték Leírás
    allocMemDF Kapcsolja be a diagnosztikai memória-kiosztót (alapértelmezett).
    delayFreeMemDF A memória felszabadításának késleltetése híváskor delete vagy free a program kilépéséig. Ez azt eredményezi, hogy a program a lehető legnagyobb mennyiségű memóriát foglalja le.
    checkAlwaysMemDF Használd az AfxCheckMemory-t minden alkalommal, amikor memóriát foglalsz le vagy szabadítasz fel.

    Ezek az értékek logikai VAGY művelet végrehajtásával kombinálva használhatók, ahogy az itt látható:

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

Memória-pillanatképek készítése

  1. Hozzon létre egy CMemoryState objektumot, és hívja meg a CMemoryState::Checkpoint tag függvényt. Ez létrehozza az első memória-pillanatképet.

  2. Miután a program végrehajtotta a memóriafoglalási és felszabadítási műveleteket, hozzon létre egy másik CMemoryState objektumot, és hívja meg Checkpoint az arra vonatkozó objektumot. Ez egy második pillanatképet kap a memóriahasználatról.

  3. Hozzon létre egy harmadik CMemoryState objektumot, és hívja meg a CMemoryState::D ifference tagfüggvényt, amely argumentumként adja meg az előző CMemoryState két objektumot. Ha különbség van a két memóriaállapot között, a Difference függvény nemero értéket ad vissza. Ez azt jelzi, hogy egyes memóriablokkok nincsenek felszabadítva.

    Ez a példa a kód megjelenését mutatja be:

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

    Figyelje meg, hogy a memória-ellenőrző utasításokat #ifdef _DEBUG/ #endif blokkok szögletes zárójelbe teszik, hogy csak a program hibakeresési verzióiban legyenek lefordítva.

    Most, hogy már tudja, hogy létezik memóriaszivárgás, használhat egy másik tagfüggvényt, a CMemoryState::DumpStatistics függvényt, amely segít megtalálni.

Memóriastatisztikák megtekintése

A CMemoryState::Difference függvény két memóriaállapot-objektumot vizsgál meg, és észleli azokat az objektumokat, amelyeket nem szabadítottak fel a halomból az első és a végállapot között. Miután memória-pillanatképeket készített, és összehasonlította őket a CMemoryState::Difference használatával, meghívhatja a CMemoryState::DumpStatistics parancsot, hogy információt kapjon a nem felszabadított objektumokról.

Vegye figyelembe a következő példát:

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

Az példában szereplő mintakivonat így néz ki:

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

A szabad blokkok olyan blokkok, amelyek felszabadítása késleltetve van, ha afxMemDFdelayFreeMemDF-re van állítva.

A második sorban látható szokásos objektumblokkok a halomon maradnak lefoglalva.

A nem objektumblokkok közé tartoznak a tömbök és a newvelük lefoglalt struktúrák. Ebben az esetben négy nem objektumblokk került lefoglalásra a kupacra, de nem lettek felszabadítva.

Largest number used a program által használt maximális memóriát adja meg.

Total allocations a program által használt memória teljes mennyiségét adja meg.

Objektumdumpek készítése

Egy MFC-programban a CMemoryState::DumpAllObjectsSince függvényt használhatja a fel nem szabadított halom összes objektumának leírásának kiírásához. DumpAllObjectsSince kiírja az összes, az utolsó CMemoryState::Ellenőrzőpont óta lefoglalt objektumot. Ha nem történt Checkpoint hívás, DumpAllObjectsSince kiírja a memóriában jelenleg lévő összes objektumot és nemobjektumot.

Megjegyzés:

Az MFC-objektumok memóriaképének használatához engedélyeznie kell a diagnosztikai nyomkövetést.

Megjegyzés:

Az MFC automatikusan kiírja az összes kiszivárgott objektumot, amikor a program bezáródik, így nem kell kódot írnia az objektumok kiírásához.

Az alábbi kód két memóriaállapot összehasonlításával teszteli a memóriaszivárgást, és minden objektumot kivesz, ha szivárgást észlel.

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

Az adatmentés tartalma a következőképpen néz ki:

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

A legtöbb sor elején lévő zárójelek száma határozza meg az objektumok lefoglalásának sorrendjét. A legújabban lefoglalt objektum a legmagasabb számmal rendelkezik, és a dumpszelet tetején jelenik meg.

Ha egy objektum memóriaképéből a lehető legtöbb információt szeretné kinyerni, felülírhatja bármely DumpCObject-ből származó objektum tagfüggvényét az objektum memóriaképének testreszabásához.

Egy adott memóriafoglalás töréspontját úgy állíthatja be, hogy a globális változót _afxBreakAlloc a zárójelekben látható számra állítja. Ha újrafuttatja a programot, a hibakereső megszakítja a végrehajtást a foglalás végrehajtásakor. Ezután megtekintheti a hívási vermet, hogy lássa, hogyan jutott el a program erre a pontra.

A C futásidejű kódtár hasonló függvényt ( _CrtSetBreakAlloc) használ a C futásidejű foglalásokhoz.

Memóriakivonatok értelmezése

Tekintse meg részletesebben ezt az objektumdumpot:

{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

A dumpot létrehozó programnak csak két explicit lefoglalása volt: egy a veremben, egy pedig a halomban.

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

A CPerson konstruktor három olyan argumentumot vesz fel, amelyek mutatók char-ra, és ezeket a CString tagváltozók inicializálásához használják. A memóriaképben az CPerson objektum három, nem objektum jellegű blokkal (3, 4 és 5) együtt látszik. Ezek tartják a CString tagváltozók karaktereit, és nem törlődnek, amikor az CPerson objektum-destruktora meghívásra kerül.

A 2. blokkszám maga az CPerson objektum. $51A4 a blokk címét jelöli, amelyet az objektum tartalma követ, amely a következő kimenetet eredményezte CPerson:Dump amikor a DumpAllObjectsSince meghívja.

Azt is kitalálhatja, hogy az 1. blokkszám a CString keretváltozóhoz van társítva a sorozatszám és a méret miatt, amely megegyezik a keretváltozóban CString lévő karakterek számával. A kereten lefoglalt változók automatikusan felszabadulnak, amikor a keret kikerül a hatókörből.

Keretváltozók

Általában nem kell aggódnia a keretváltozókhoz társított halomobjektumok miatt, mert a rendszer automatikusan felszabadítja őket, amikor a keretváltozók kimennek a hatókörből. A memóriadiagnosztikai memóriaképek zsúfoltságának elkerülése érdekében a hívásokat Checkpoint úgy kell elhelyeznie, hogy azok a keretváltozók hatókörén kívül legyenek. Helyezze például a hatókör zárójeleit az előző foglalási kód köré, az itt látható módon:

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

A hatókör zárójelei között a példa memóriaképe a következő:

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

Nem objektumhoz kapcsolódó allokációk

Figyelje meg, hogy egyes foglalások objektumok (például CPerson), míg mások nem objektum foglalások. A "nem objektum típusú foglalások" olyan foglalások, amelyek nem CObject-ből származó objektumokhoz vagy olyan primitív C-típusok foglalásai, mint a char, int, vagy long tartoznak. Ha a CObject-alapú osztály további területet foglal le, például belső pufferek számára, ezek az objektumok objektum- és nem akadálymentes foglalásokat is megjelenítenek.

Memóriavesztések megelőzése

Figyelje meg a fenti kódban, hogy a keretváltozóhoz CString társított memóriablokk automatikusan felszabadítva lett, és nem jelenik meg memóriaszivárgásként. A hatókörkezelési szabályokhoz társított automatikus felszabadítás gondoskodik a keretváltozókhoz társított memóriaszivárgások többségéről.

A halomra lefoglalt objektumok esetében azonban explicit módon törölnie kell az objektumot, hogy megakadályozza a memóriaszivárgást. Az előző példában szereplő utolsó memóriaszivárgás törléséhez törölje a CPerson halomra lefoglalt objektumot az alábbiak szerint:

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

Objektumképek testreszabása

Amikor a CObject osztályt hoz létre, felülbírálhatja a Dump tagfüggvényt, hogy további információkat adjon meg, amikor a DumpAllObjectsSince parancsot használja az objektumok kimeneti ablakba való memóriaképéhez.

A Dump függvény az objektum tagváltozóinak szöveges ábrázolását írja egy memóriakép-környezetbe (CDumpContext). A mentési kontextus hasonló egy I/O adatfolyamhoz. A hozzáfűző operátorral (<<) adatokat küldhet egy CDumpContext.

A függvény felülbírálásakor Dump először az alaposztály-verziót Dump kell meghívnia az alaposztály-objektum tartalmának kiírásához. Ezután adjon meg egy szöveges leírást és értéket a származtatott osztály minden tagváltozójához.

A függvény deklarációja a Dump következőképpen néz ki:

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

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

Mivel az objektum dumpolásnak csak akkor van értelme, ha éppen a program hibakeresését végzi, a Dump függvény deklarációja egy #ifdef _DEBUG/#endif blokkban van határolva.

Az alábbi példában a Dump függvény először az alaposztályához hívja meg a Dump függvényt. Ezután egy rövid leírást ír az egyes tagváltozókról a tag értékével együtt a diagnosztikai streambe.

#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

Meg kell adnia egy argumentumot CDumpContext , amely megadja a memóriakép kimenetének helyét. Az MFC hibakeresési verziója egy előre definiált CDumpContext objektumot biztosít, afxDump amely kimenetet küld a hibakeresőnek.

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

MFC hibakeresési build méretének csökkentése

A nagy MFC-alkalmazások hibakeresési adatai sok lemezterületet foglalnak el. A méret csökkentéséhez az alábbi eljárások egyikét használhatja:

  1. Az MFC-kódtárak újraépítése a /Z7, /Zi és /ZI (Hibakeresési információformátum) beállítással a /Z7 helyett. Ezek a lehetőségek egyetlen programadatbázist (PDB) építenek ki, amely a teljes tár hibakeresési adatait tartalmazza, csökkentve a redundanciát és a helymegtakarítást.

  2. Az MFC-kódtárak újraépítése hibakeresési információk nélkül (nincs /Z7, /Zi, /ZI (Hibakeresési információformátum) beállítás. Ebben az esetben a hibakeresési információk hiánya megakadályozza, hogy a legtöbb hibakereső funkciót használja az MFC könyvtárban, de mivel az MFC könyvtárak már alaposan ki vannak javítva, ez nem feltétlenül probléma.

  3. Saját alkalmazást hozhat létre a kiválasztott modulok hibakeresési információival, csak az alábbiak szerint.

MFC-alkalmazás létrehozása a kiválasztott modulok hibakeresési információival

A kiválasztott modulok MFC hibakeresési kódtárakkal való létrehozása lehetővé teszi a léptetés és a modulok egyéb hibakeresési lehetőségeinek használatát. Ez az eljárás a projekt hibakeresési és kiadási konfigurációit is használja, így szükségessé teszi a következő lépésekben leírt módosításokat (és a teljes kiadási buildhez szükséges "újraépítést" is).

  1. A Megoldáskezelőben válassza ki a projektet.

  2. A Nézet menüben válassza a Tulajdonságlapok lehetőséget.

  3. Először egy új projektkonfigurációt fog létrehozni.

    1. <A Projekttulajdonságok> lapjai párbeszédpanelen kattintson a Configuration Manager gombra.

    2. A Configuration Manager párbeszédpanelen keresse meg a projektet a rácson. A Konfiguráció oszlopban válassza az Új...< lehetőséget>.

    3. Az Új projektkonfiguráció párbeszédpanelen írja be az új konfiguráció nevét (például "Részleges hibakeresés" ) a Projektkonfiguráció neve mezőbe.

    4. A Másolási beállítások listában válassza a Kiadás lehetőséget.

    5. Kattintson az OK gombra az Új projektkonfiguráció párbeszédpanel bezárásához.

    6. Zárja be a Configuration Manager párbeszédpanelt.

  4. Most megadhatja a teljes projekt beállításait.

    1. A Tulajdonságlapok párbeszédpanel Konfiguráció tulajdonságai mappájában válassza ki az Általános kategóriát.

    2. A projektbeállítások rácsában bontsa ki a Project Defaults (ha szükséges) elemet.

    3. A Projekt alapértelmezései csoportban keresse meg az MFC használatát. Az aktuális beállítás a rács jobb oszlopában jelenik meg. Kattintson az aktuális beállításra, és módosítsa az MFC-t statikus könyvtárként történő használatra.

    4. A Tulajdonságok lapjai párbeszédpanel bal oldali ablaktábláján nyissa meg a C/C++ mappát, és válassza az Előfeldolgozó lehetőséget. A tulajdonságok rácsában keresse meg az előfeldolgozási definíciókat , és cserélje le az "NDEBUG" kifejezést a "_DEBUG" kifejezésre.

    5. A Tulajdonságok lapjai párbeszédpanel bal oldali ablaktábláján nyissa meg a Linker mappát, és válassza ki a Beviteli kategóriát . A tulajdonságok rácsában keresse meg a további függőségeket. A További függőségek beállításba írja be a következőt: "NAFXCWD. LIB" és "LIBCMT".

    6. Az OK gombra kattintva mentse az új buildbeállításokat, és zárja be a Tulajdonságlapok párbeszédpanelt.

  5. A Build menüben válassza az Újraépítés lehetőséget. Ez eltávolítja az összes hibakeresési információt a modulokból, de nem befolyásolja az MFC-kódtárat.

  6. Most vissza kell adnia a hibakeresési információkat az alkalmazás kijelölt moduljaihoz. Ne feledje, hogy töréspontokat állíthat be, és más hibakereső függvényeket csak a hibakeresési adatokkal összeállított modulokban hajthat végre. Minden olyan projektfájl esetében, amelyben hibakeresési információkat szeretne felvenni, hajtsa végre a következő lépéseket:

    1. A Megoldáskezelőben nyissa meg a projekt alatt található Forrásfájlok mappát.

    2. Jelölje ki azt a fájlt, amelyhez hibakeresési adatokat szeretne beállítani.

    3. A Nézet menüben válassza a Tulajdonságlapok lehetőséget.

    4. A Tulajdonságlapok párbeszédpanel Konfigurációs beállítások mappájában nyissa meg a C/C++ mappát, majd válassza ki az Általános kategóriát.

    5. A tulajdonságok rácsában keresse meg a Hibakeresési információformátumot.

    6. Kattintson a Hibakeresési információformátum beállításaira, és válassza ki a kívánt beállítást (általában /ZI) a hibakeresési információkhoz.

    7. Ha alkalmazásvarázsló által létrehozott alkalmazást használ, vagy előre összeállított fejlécekkel rendelkezik, ki kell kapcsolnia az előre összeállított fejléceket, vagy újra kell fordítania őket a többi modul összeállítása előtt. Ellenkező esetben a C4650 figyelmeztetés és a C2855 hibaüzenet jelenik meg. Az előre összeállított fejléceket kikapcsolhatja a módosításával (< mappa, >C/C++ almappák, Előre összeállított fejlécek kategória).

  7. A Build menüben válassza a Build lehetőséget az elavult projektfájlok újraépítéséhez.

    A jelen témakörben ismertetett módszer alternatívaként egy külső makefile-fájllal határozhatja meg az egyes fájlok egyéni beállításait. Ebben az esetben az MFC hibakeresési kódtárakkal való csatoláshoz meg kell határoznia az egyes modulokhoz tartozó _DEBUG jelzőt. Ha MFC kiadási kódtárakat szeretne használni, meg kell határoznia az NDEBUG-ot. A külső makefile-k írásával kapcsolatos további információkért tekintse meg az NMAKE referenciafájljait.