Tecniche di debug MFC
Se si effettua il debug di un programma MFC, possono essere utili le seguenti tecniche di debug.
Contenuto dell'argomento
Rilevamento di perdite di memoria in MFC
AfxDebugBreak
MFC offre una speciale funzione AfxDebugBreak per programmare i punti di interruzione nel codice sorgente:
AfxDebugBreak( );
Su piattaforme Intel AfxDebugBreak
genera il seguente codice, che interrompe il codice sorgente anziché il codice kernel:
_asm int 3
Su altre piattaforme, AfxDebugBreak
richiama semplicemente DebugBreak
.
Accertarsi di rimuovere le istruzioni AfxDebugBreak
quando si crea una build di rilascio o si utilizza #ifdef _DEBUG
per racchiuderle.
Utilizzo della macro TRACE
Per visualizzare i messaggi generati dal programma nella finestra di outputdel debugger è possibile usare la macro ATLTRACE oppure la macro TRACE MFC. Analogamente alle asserzionile macro di traccia sono attive solo nella versione di debug del programma e vengono annullate quando ne viene effettuata la compilazione nella versione di rilascio.
Gli esempi che seguono illustrano alcuni metodi di utilizzo della macro TRACE . Analogamente a printf
la macro TRACE è in grado di gestire svariati argomenti.
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 );
La macro TRACE gestisce in modo appropriato i parametri char* e wchar_t*. Negli esempi seguenti viene illustrato l'utilizzo della macro TRACE, insieme ai diversi tipi di parametri di stringa.
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);
Per altre informazioni sulla macro TRACE , vedere Servizi diagnostici.
Rilevamento di perdite di memoria in MFC
MFC offre classi e funzioni per il rilevamento della memoria allocata ma mai disallocata.
Gestione delle allocazioni di memoria
In MFC è possibile usare la macro DEBUG_NEW anziché l'operatore new per un supporto nella ricerca di perdite di memoria. Nella versione di debug del programma, DEBUG_NEW
tiene traccia del nome file e del numero di riga di ciascun oggetto da esso allocato. Quando si compila una versione di rilascio del programma, DEBUG_NEW
si traduce in una semplice operazione new senza informazioni relative a nome file e numero di riga. La velocità non viene pertanto in alcun modo compromessa nella versione di rilascio del programma.
Se non si desidera riscrivere l'intero programma in modo da utilizzare DEBUG_NEW
anziché new, sarà possibile definire questa macro nei file sorgente:
#define new DEBUG_NEW
Quando si esegue un dump di oggetti, ciascun oggetto allocato con DEBUG_NEW
indicherà il file e il numero di riga in cui è stato allocato, consentendo di risalire all'origine delle perdite di memoria.
La versione di debug del framework MFC utilizza DEBUG_NEW
automaticamente, a differenza del codice. Se si desidera godere dei vantaggi di DEBUG_NEW
, sarà necessario utilizzare DEBUG_NEW
esplicitamente oppure #define new come illustrato in precedenza.
Abilitazione della diagnostica di memoria
Per poter utilizzare le utilità di diagnostica della memoria è necessario attivare la traccia diagnostica.
Per abilitare o disabilitare la diagnostica della memoria
Chiamare la funzione globale AfxEnableMemoryTracking per abilitare o disabilitare l'allocatore di memoria diagnostico. Dal momento che la diagnostica della memoria si trova per impostazione predefinita nella libreria di debug, in genere si ricorre a questa funzione per disattivare tale diagnostica temporaneamente, aumentando la velocità di esecuzione del programma e riducendo l'output di diagnostica.
Per selezionare caratteristiche specifiche di diagnostica della memoria con afxMemDF
Se si desidera godere di un maggior controllo sulle caratteristiche di diagnostica della memoria, sarà possibile attivare e disattivare selettivamente singole caratteristiche di diagnostica della memoria impostando il valore della variabile globale MFC afxMemDF. A questa variabile è possibile assegnare i seguenti valori, come specificato dal tipo enumerato afxMemDF.
Valore Descrizione allocMemDF Attiva l'allocatore di memoria diagnostica (impostazione predefinita). delayFreeMemDF Liberare memoria quando si chiama delete
ofree
solo dopo la chiusura del programma. In questo modo il programma allocherà la maggior quantità possibile di memoria.checkAlwaysMemDF Chiama AfxCheckMemory ogni volta che viene allocata o liberata memoria. È inoltre possibile usare combinazioni di questi valori eseguendo un'operazione OR logica, come illustrato di seguito:
afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
Creazione di snapshot di memoria
Creare un oggetto CMemoryState e chiamare la funzione membro CMemoryState::Checkpoint . Verrà creato il primo snapshot della memoria.
Dopo che il programma ha eseguito le operazioni di allocazione e disallocazione di memoria, creare un altro oggetto
CMemoryState
e chiamareCheckpoint
per tale oggetto. Verrà creato un secondo snapshot dell'utilizzo della memoria.Creare un terzo oggetto
CMemoryState
e chiamarne la funzione membro CMemoryState::Difference , fornendo come argomenti i due oggettiCMemoryState
precedenti. Se esiste una differenza tra i due stati della memoria, la funzioneDifference
restituirà un valore diverso da zero, indicando così che alcuni blocchi di memoria non sono stati disallocati.Il codice sarà del tipo riportato in questo esempio:
// 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
Si noti che le istruzioni di controllo della memoria sono racchiuse tra parentesi tra blocchi #ifdef _DEBUG/#endif in modo che vengano compilate solo nelle versioni di debug del programma.
Determinata l'esistenza di una perdita di memoria, sarà possibile usare un'altra funzione membro, CMemoryState::DumpStatistics , che consentirà di individuare la perdita.
Visualizzazione delle statistiche di memoria
La funzione CMemoryState::Difference esamina due oggetti stato di memoria e rileva eventuali oggetti non deallocati dall'heap tra gli stati iniziale e finale. Dopo aver creato snapshot di memoria e averli confrontati usando CMemoryState::Difference
, è possibile chiamare CMemoryState::DumpStatistics per ottenere informazioni sugli oggetti non deallocati.
Si consideri l'esempio seguente:
if( diffMemState.Difference( oldMemState, newMemState ) )
{
TRACE( "Memory leaked!\n" );
diffMemState.DumpStatistics();
}
Un dump campione sarà del seguente tipo:
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
I blocchi liberi rappresentano blocchi la cui deallocazione viene ritardata se afxMemDF
è stato impostato su delayFreeMemDF
.
I blocchi di oggetti ordinari, indicati alla seconda riga, rimangono allocati sull'heap.
I blocchi non di oggetti includono matrici e strutture allocate con new
. In questo caso quattro blocchi non di oggetti sono stati allocati sull'heap ma non disallocati.
Largest number used
indica la quantità massima di memoria utilizzata dal programma in qualsiasi momento.
Total allocations
indica la quantità totale di memoria utilizzata dal programma.
Creazione di dump di oggetti
In un programma MFC è possibile usare CMemoryState::D umpAllObjectsSince per eseguire il dump di una descrizione di tutti gli oggetti nell'heap che non sono stati deallocati. DumpAllObjectsSince
esegue il dump di tutti gli oggetti allocati dall'ultima CMemoryState::Checkpoint. Se non ha avuto luogo alcuna chiamata Checkpoint
, DumpAllObjectsSince
effettuerà il dump di tutti gli oggetti e non oggetti attualmente in memoria.
Nota
Prima di poter utilizzare il dump di oggetti MFC, è necessario abilitare la traccia di diagnostica.
Nota
MFC effettua automaticamente il dump di tutti gli oggetti persi alla chiusura del programma, pertanto non è necessario creare codice per il dump degli oggetti in tale posizione.
Nel codice che segue si verifica la presenza di eventuali perdite di memoria confrontando due stati della memoria e si effettua il dump di tutti gli oggetti eventualmente persi.
if( diffMemState.Difference( oldMemState, newMemState ) )
{
TRACE( "Memory leaked!\n" );
diffMemState.DumpAllObjectsSince();
}
Il contenuto del dump sarà del seguente tipo:
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
I numeri tra parentesi graffe all'inizio della maggior parte delle righe specificano l'ordine in cui gli oggetti sono stati allocati. L'ultimo oggetto allocato presenta il numero più alto e si trova all'inizio del dump.
Per ottenere la quantità massima di informazioni da un dump di oggetti, è possibile eseguire l'override della funzione membro Dump
di qualsiasi oggetto derivato da CObject
per personalizzare il dump di oggetti.
È possibile definire un punto di interruzione su una particolare allocazione di memoria impostando la variabile globale _afxBreakAlloc
sul numero indicato tra le parentesi graffe. Se si esegue nuovamente il programma il debugger interromperà l'esecuzione quando avrà luogo tale allocazione. Sarà quindi possibile esaminare lo stack di chiamate per sapere come il programma è giunto a tale punto.
La libreria di run-time del linguaggio C dispone di una funzione simile, _CrtSetBreakAlloc, che è possibile usare per le allocazioni di run-time del linguaggio C.
Interpretazione dei dump di memoria
Considerare questo dump di oggetti in maggior dettaglio:
{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
Nel programma che ha generato questo dump erano presenti solo due allocazioni esplicite, una sullo stack e una sull'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" );
Il costruttore CPerson
accetta tre argomenti che rappresentano puntatori a char
, utilizzati per inizializzare variabili membro CString
. Nel dump della memoria è possibile individuare l'oggetto CPerson
nonché tre blocchi non di oggetti (3, 4 e 5). Questi blocchi contengono i caratteri delle variabili membro CString
e non verranno eliminati quando sarà chiamato il distruttore dell'oggetto CPerson
.
Il blocco il numero 2 è l'oggetto CPerson
stesso. $51A4
rappresenta l'indirizzo del blocco ed è seguito dal contenuto dell'oggetto, restituito come output da CPerson
::Dump
quando è stato chiamato da DumpAllObjectsSince.
È chiaro che il blocco numero 1 è associato alla variabile di frame CString
, in quanto presenta una sequenza di numero e dimensione che corrisponde al numero di caratteri della variabile di frame CString
. Le variabili allocate sul frame vengono automaticamente disallocate quando il frame esce dall'ambito consentito.
Variabili di frame
In generale gli oggetti degli heap associati a variabili di frame non devono destare preoccupazioni, poiché vengono automaticamente disallocati quando le variabili di frame escono dall'ambito consentito. Per far sì che i dump di diagnostica della memoria non siano eccessivamente ingombranti, è opportuno inserire le chiamate a Checkpoint
in modo che restino all'esterno dell'ambito delle variabili di frame. Inserire, ad esempio, il precedente codice di allocazione tra parentesi indicanti l'ambito, come illustrato di seguito:
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();
Dopo l'inserimento delle parentesi di ambito, il dump della memoria di questo esempio sarà il seguente:
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
Allocazioni di non oggetti
Si noti che alcune allocazioni sono di oggetti (ad esempio CPerson
) mentre altre sono allocazioni di non oggetti. Queste ultime sono allocazioni di oggetti non derivati da CObject
o allocazioni di tipi C primitivi, ad esempio char
, int
o long
. Se la classe derivata da CObject-alloca ulteriore spazio, ad esempio per buffer interni, questi oggetti presenteranno allocazioni di oggetti e di non oggetti.
Prevenzione di perdite di memoria
Si noti che nel codice precedente il blocco di memoria associato alla variabile di frame CString
è stato disallocato automaticamente e non appare quindi come una perdita di memoria. La disallocazione automatica associata alle regole di ambito gestisce la maggior parte delle perdite di memoria associati alle variabili di frame.
Per gli oggetti allocati sull'heap, tuttavia, è necessario eliminare esplicitamente l'oggetto per impedire una perdita di memoria. Per eliminare l'ultima perdita di memoria dell'esempio precedente, eliminare l'oggetto CPerson
allocato sull'heap, come segue:
{
// 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;
}
Personalizzazione di dump di oggetti
Quando si deriva una classe da CObject, è possibile eseguire l'override della funzione membro Dump
per fornire ulteriori informazioni quando si usa DumpAllObjectsSince per eseguire il dump di oggetti nella finestra Output.
La funzione Dump
scrive una rappresentazione testuale delle variabili membro dell'oggetto in un contesto di dump (CDumpContext). Il contesto di dump è analogo a un flusso I/O. È possibile usare l'operatore APPEND (<<) per inviare dati a CDumpContext
.
Quando si esegue l'override della funzione Dump
, è opportuno chiamare dapprima la versione della classe base di Dump
per effettuare il dump del contenuto dell'oggetto classe base, generando poi una descrizione testuale e un valore per ciascuna variabile membro della classe derivata.
La dichiarazione della funzione Dump
sarà del seguente tipo:
class CPerson : public CObject
{
public:
#ifdef _DEBUG
virtual void Dump( CDumpContext& dc ) const;
#endif
CString m_firstName;
CString m_lastName;
// And so on...
};
Dal momento che il dump di oggetti ha senso solo quando si effettua il debug del programma, la dichiarazione della funzione Dump
è racchiusa all'interno di un blocco #ifdef _DEBUG / #endif .
Nell'esempio che segue la funzione Dump
chiama dapprima la funzione Dump
della relativa classe base e poi scrive nel flusso diagnostico una breve descrizione di ciascuna variabile membro, insieme al valore del membro.
#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
È necessario fornire un argomento CDumpContext
per specificare la posizione in cui verrà generato l'output del dump. La versione di debug di MFC offre un oggetto CDumpContext
predefinito denominato afxDump
che invia l'output al debugger.
CPerson* pMyPerson = new CPerson;
// Set some fields of the CPerson object.
//...
// Now dump the contents.
#ifdef _DEBUG
pMyPerson->Dump( afxDump );
#endif
Riduzione delle dimensioni di una build di debug di MFC
Le informazioni di debug di un'applicazione MFC di grandi dimensioni possono richiedere notevole spazio su disco. Per ridurre le dimensioni, è possibile usare una di queste procedure:
Ricompilare le librerie MFC usando l'opzione /Z7, /Zi, /ZI (Debug Information Format) anziché /Z7. Queste opzioni compilano un singolo file di database di programma (PDB) che contiene informazioni di debug per l'intera libreria, riducendo la ridondanza e risparmiando spazio.
Ricompilare le librerie MFC senza informazioni di debug (nessuna opzione /Z7, /Zi, /ZI (Formato informazioni di debug). In questo caso la mancanza delle informazioni di debug impedirà di utilizzare la maggior parte delle utilità del debugger nel codice delle librerie MFC, ma dal momento che le librerie MFC sono già state sottoposte a un debug completo, questo non dovrebbe rappresentare un problema.
Compilare l'applicazione con informazioni di debug solo per i moduli selezionati, come descritto di seguito.
Compilazione di un'app MFC con informazioni di debug per i moduli selezionati
La compilazione di moduli selezionati con le librerie di debug MFC consente di eseguire i moduli un'istruzione alla volta e di utilizzare altre utilità di debug. Questa procedura usa sia le configurazioni di debug che di rilascio del progetto, richiedendo quindi le modifiche descritte nei passaggi seguenti (e rendendo necessaria anche una "ricompilazione completa" quando è necessaria una build di rilascio completa).
In Esplora soluzioni selezionare il progetto.
Scegliere Pagine delle proprietà dal menu Visualizza.
Creare innanzitutto una nuova configurazione di progetto.
<Nella finestra di dialogo Pagine delle proprietà del progetto> fare clic sul pulsante Configuration Manager.
Nella finestra di dialogo Gestione configurazioneindividuare il progetto all'interno della griglia. Nella colonna Configurazione selezionare <Nuovo.>
Nella finestra di dialogo Nuova configurazione progettodigitare, all'interno della casella Nome configurazione progetto , il nome da assegnare alla nuova configurazione, ad esempio "Debug parziale".
Scegliere Release dall'elenco Copia impostazioni da.
Fare clic su OK per chiudere la finestra di dialogo Nuova configurazione progetto.
Chiudere la finestra di dialogo Gestione configurazione .
Impostare ora le opzioni desiderate per l'intero progetto.
Nella finestra di dialogo Pagine delle proprietà , sotto la cartella Proprietà di configurazione , selezionare la categoria Generale .
Nella griglia delle impostazioni del progetto espandere Impostazioni predefinite progetto (se necessario).
In Impostazioni predefinite progettotrovare Uso di MFC. L'impostazione corrente verrà visualizzata nella colonna di destra della griglia. Fare clic sull'impostazione corrente e modificarla in Usa MFC in una libreria statica.
Nel riquadro di sinistra della finestra di dialogo Pagine delle proprietà aprire la cartella C/C++ e selezionare Preprocessore. Nella griglia delle proprietà cercare Definizioni preprocessore e sostituire "NDEBUG" con "_DEBUG".
Nel riquadro di sinistra della finestra di dialogo Pagine delle proprietà aprire la cartella Linker e selezionare la categoria Input . Nella griglia delle proprietà cercare Dipendenze aggiuntive. Nell'impostazione Dipendenze aggiuntive digitare "NAFXCWD.LIB" e "LIBCMT."
Scegliere OK per salvare le nuove opzioni di compilazione e chiudere la finestra di dialogo Pagine delle proprietà .
Scegliere Ricompila dal menu Compila. Verranno così rimosse tutte le informazioni di debug dai moduli, ma non verrà apportata alcuna modifica alla libreria MFC.
A questo punto è necessario aggiungere nuovamente le informazioni di debug ai moduli selezionati nell'applicazione. Tenere presente che è possibile impostare punti di interruzione ed eseguire altre funzioni di debug solo nei moduli che sono stati compilati con informazioni di debug. Per ciascun file di progetto in cui si desidera includere informazioni di debug, eseguire i seguenti passaggi:
In Esplora soluzioni aprire la cartella File di origine sotto il progetto.
Selezionare il file per il quale si desidera impostare informazioni di debug.
Scegliere Pagine delle proprietà dal menu Visualizza.
Nella finestra di dialogo Pagine delle proprietà , sotto la cartella Impostazioni di configurazione , aprire la cartella C/C++ , quindi selezionare la categoria Generale .
Nella griglia delle proprietà cercare Formato informazioni di debug.
Fare clic sulle impostazioni Formato informazioni di debug e selezionare l'opzione desiderata (in genere /ZI) per le informazioni di debug.
Se si usa un'applicazione generata mediante una creazione guidata di applicazioni o si fa uso di intestazioni precompilate, sarà necessario disattivare tali intestazioni o compilarle nuovamente prima di compilare gli altri moduli. In caso contrario, verranno generati l'avviso C4650 e il messaggio di errore C2855. È possibile disattivare le intestazioni precompilate modificando l'impostazione Crea/Usa intestazioni precompilate nella< finestra di dialogo Proprietà progetto> (cartella Proprietà di configurazione, sottocartella C/C++, categoria Intestazioni precompilate).
Scegliere Compila dal menu Compila per compilare nuovamente i file di progetto non aggiornati.
In alternativa alla tecnica descritta in questo argomento è possibile usare un makefile esterno per definire singole opzioni per ciascun file. In tal caso, per collegarsi alle librerie di debug MFC, sarà necessario definire il flag _DEBUG per ciascun modulo. Per utilizzare le librerie di rilascio MFC, è necessario definire NDEBUG. Per altre informazioni sulla scrittura di makefile esterni, vedere Riferimenti a NMAKE.