Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
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 deletevagyfreea 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
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.
Miután a program végrehajtotta a memóriafoglalási és felszabadítási műveleteket, hozzon létre egy másik
CMemoryStateobjektumot, és hívja megCheckpointaz arra vonatkozó objektumot. Ez egy második pillanatképet kap a memóriahasználatról.Hozzon létre egy harmadik
CMemoryStateobjektumot, és hívja meg a CMemoryState::D ifference tagfüggvényt, amely argumentumként adja meg az előzőCMemoryStatekét objektumot. Ha különbség van a két memóriaállapot között, aDifferencefü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" ); } #endifFigyelje 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:
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.
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.
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).
A Megoldáskezelőben válassza ki a projektet.
A Nézet menüben válassza a Tulajdonságlapok lehetőséget.
Először egy új projektkonfigurációt fog létrehozni.
<A Projekttulajdonságok> lapjai párbeszédpanelen kattintson a Configuration Manager gombra.
A Configuration Manager párbeszédpanelen keresse meg a projektet a rácson. A Konfiguráció oszlopban válassza az Új...< lehetőséget>.
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.
A Másolási beállítások listában válassza a Kiadás lehetőséget.
Kattintson az OK gombra az Új projektkonfiguráció párbeszédpanel bezárásához.
Zárja be a Configuration Manager párbeszédpanelt.
Most megadhatja a teljes projekt beállításait.
A Tulajdonságlapok párbeszédpanel Konfiguráció tulajdonságai mappájában válassza ki az Általános kategóriát.
A projektbeállítások rácsában bontsa ki a Project Defaults (ha szükséges) elemet.
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.
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.
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".
Az OK gombra kattintva mentse az új buildbeállításokat, és zárja be a Tulajdonságlapok párbeszédpanelt.
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.
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:
A Megoldáskezelőben nyissa meg a projekt alatt található Forrásfájlok mappát.
Jelölje ki azt a fájlt, amelyhez hibakeresési adatokat szeretne beállítani.
A Nézet menüben válassza a Tulajdonságlapok lehetőséget.
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.
A tulajdonságok rácsában keresse meg a Hibakeresési információformátumot.
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.
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).
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.