Condividi tramite


Tecniche di debug MFC

Se si esegue il debug di un programma MFC, queste tecniche di debug possono essere utili.

AfxDebugBreak

MFC fornisce una funzione speciale AfxDebugBreak per codificare staticamente i punti di interruzione nel codice sorgente.

AfxDebugBreak( );

Nelle piattaforme Intel produce AfxDebugBreak il codice seguente, che interrompe il codice sorgente anziché il codice kernel:

_asm int 3

In altre piattaforme, AfxDebugBreak chiama DebugBreaksemplicemente .

Assicurarsi di rimuovere AfxDebugBreak le istruzioni quando si crea una build di versione o utilizzare #ifdef _DEBUG per racchiuderle.

Macro TRACE

Per visualizzare i messaggi dal programma nella finestra Output del debugger, è possibile utilizzare la macro ATLTRACE o la macro TRACE MFC. Analogamente alle asserzioni, le macro di traccia sono attive solo nella versione Debug del programma e scompaiono quando vengono compilate nella versione release.

Negli esempi seguenti vengono illustrati alcuni dei modi in cui è possibile utilizzare la macro TRACE . Come printf, la macro TRACE può gestire un numero di 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 con diversi tipi di parametri 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 di diagnostica.

Rilevamento di perdite di memoria in MFC

MFC fornisce classi e funzioni per il rilevamento della memoria allocata ma mai deallocata.

Rilevamento delle allocazioni di memoria

In MFC è possibile usare la macro DEBUG_NEW al posto del nuovo operatore per individuare le perdite di memoria. Nella versione Debug del programma, DEBUG_NEW tiene traccia del nome file e del numero di riga per ogni oggetto allocato. Quando compili una versione Release del tuo programma, DEBUG_NEW si risolve in una semplice operazione new senza il nome del file e le informazioni sul numero di riga. Pertanto, non si paga alcuna penalità di velocità nella versione di rilascio del programma.

Se non si vuole riscrivere l'intero programma da usare DEBUG_NEW al posto di nuovo, è possibile definire questa macro nei file di origine:

#define new DEBUG_NEW

Quando si esegue un dump dell'oggetto, ogni oggetto allocato con DEBUG_NEW mostrerà il file e il numero di riga in cui è stato allocato, consentendo di individuare le origini delle perdite di memoria.

La versione di debug del framework MFC utilizza DEBUG_NEW automaticamente, ma il tuo codice no. Per ottenere i vantaggi di DEBUG_NEW, è necessario usare DEBUG_NEW in modo esplicito o #define nuovo , come illustrato in precedenza.

Abilitazione della diagnostica della memoria

Prima di poter usare le funzionalità di diagnostica della memoria, è necessario abilitare la traccia diagnostica.

Per abilitare o disabilitare la diagnostica della memoria

  • Chiamare la funzione globale AfxEnableMemoryTracking per abilitare o disabilitare l'allocatore di memoria diagnostica. Poiché la diagnostica della memoria è attivata per impostazione predefinita nella libreria di debug, in genere si userà questa funzione per disattivarle temporaneamente, aumentando la velocità di esecuzione del programma e riducendo l'output di diagnostica.

    Per selezionare funzionalità di diagnostica della memoria specifiche con afxMemDF

  • Se si vuole un controllo più preciso sulle funzionalità di diagnostica della memoria, è possibile attivare e disattivare in modo selettivo le singole funzionalità di diagnostica della memoria impostando il valore della variabile globale MFC afxMemDF. Questa variabile può avere i valori seguenti, come specificato dal tipo enumerato afxMemDF.

    Valore Descrizione
    allocMemDF Attivare l'allocatore di memoria di diagnostica (impostazione predefinita).
    delayFreeMemDF Ritardare la liberazione della memoria durante la chiamata delete o free fino all'uscita del programma. In questo modo il programma alloca la quantità massima possibile di memoria.
    checkAlwaysMemDF Chiamare AfxCheckMemory ogni volta che la memoria viene allocata o liberata.

    Questi valori possono essere usati in combinazione eseguendo un'operazione OR logica, come illustrato di seguito:

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

Acquisizione di istantanee di memoria

  1. Creare un oggetto CMemoryState e chiamare la funzione membro CMemoryState::Checkpoint . Verrà creato il primo snapshot di memoria.

  2. Dopo che il programma esegue l'allocazione della memoria e le operazioni di deallocazione, creare un altro CMemoryState oggetto e chiamare Checkpoint per tale oggetto. Ottiene un secondo snapshot dell'utilizzo della memoria.

  3. Creare un terzo CMemoryState oggetto e chiamare la relativa funzione membro CMemoryState::Difference, specificando i due oggetti precedenti come argomenti CMemoryState. Se esiste una differenza tra i due stati di memoria, la Difference funzione restituisce un valore diverso da zero. Ciò indica che alcuni blocchi di memoria non sono stati deallocati.

    Questo esempio mostra l'aspetto del codice:

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

    Ora che sai che esiste una perdita di memoria, è possibile usare un'altra funzione membro, CMemoryState::DumpStatistics che ti consente di individuarla.

Visualizzazione delle statistiche di memoria

La funzione CMemoryState::D ifference esamina due oggetti dello stato della memoria e rileva eventuali oggetti non deallocati dall'heap tra gli stati iniziale e finale. Dopo aver acquisito istantanee di memoria e li hai confrontati usando CMemoryState::Difference, è possibile chiamare CMemoryState::DumpStatistics per ottenere informazioni sugli oggetti che non sono stati deallocati.

Si consideri l'esempio seguente:

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

Un esempio di dump appare così:

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 sono blocchi la cui deallocazione è ritardata se afxMemDF è stata impostata su delayFreeMemDF.

I blocchi di oggetti ordinari, visualizzati nella seconda riga, rimangono allocati nell'heap.

I blocchi non oggetto includono matrici e strutture allocate con new. In questo caso, quattro blocchi non oggetto sono stati allocati nell'heap ma non deallocati.

Largest number used fornisce la memoria massima usata dal programma in qualsiasi momento.

Total allocations fornisce la quantità totale di memoria utilizzata dal programma.

Acquisizione 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'ultimo CMemoryState::Checkpoint. Se non è stata eseguita alcuna Checkpoint chiamata, DumpAllObjectsSince esegue il dump di tutti gli oggetti e non oggetti attualmente in memoria.

Annotazioni

Prima di poter usare il dump degli oggetti MFC, è necessario abilitare il tracciamento diagnostico.

Annotazioni

MFC esegue automaticamente il dump di tutti gli oggetti persi all'uscita del programma, quindi non è necessario creare codice per eseguire il dump degli oggetti in quel momento.

Il codice seguente verifica una perdita di memoria confrontando due stati di memoria e esegue il dump di tutti gli oggetti se viene rilevata una perdita.

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

Il contenuto del dump è simile al 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

{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'oggetto allocato più di recente ha il numero più alto e viene visualizzato nella parte superiore del dump.

Per ottenere la quantità massima di informazioni da un dump dell'oggetto, è possibile eseguire l'override della Dump funzione membro di qualsiasi CObjectoggetto derivato da per personalizzare il dump dell'oggetto.

È possibile impostare un punto di interruzione su una determinata allocazione di memoria impostando la variabile _afxBreakAlloc globale sul numero indicato nelle parentesi graffe. Se si esegue nuovamente il programma, il debugger interromperà l'esecuzione quando viene eseguita l'allocazione. È quindi possibile esaminare lo stack di chiamate per vedere come il programma è arrivato a quel punto.

La libreria di runtime C ha una funzione simile, _CrtSetBreakAlloc, che è possibile usare per le allocazioni di runtime C.

Interpretazione dei dump della memoria

Esaminare questo dump dell'oggetto in modo più dettagliato:

{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

Il programma che ha generato questo dump aveva solo due allocazioni esplicite, una nello stack e una nell'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 CPerson costruttore accetta tre argomenti che sono puntatori a char, che vengono usati per inizializzare le CString variabili membro. Nel dump della memoria è possibile visualizzare l'oggetto CPerson insieme a tre blocchi non oggetto (3, 4 e 5). Questi contengono i caratteri per le CString variabili membro e non verranno eliminati quando viene richiamato il distruttore dell'oggetto CPerson .

Il numero di blocco 2 è l'oggetto CPerson stesso. $51A4 rappresenta l'indirizzo del blocco ed è seguito dal contenuto dell'oggetto, che sono stati restituiti da CPerson::Dump quando viene chiamato da DumpAllObjectsSince.

È possibile indovinare che il numero di blocco 1 è associato alla variabile di CString fotogramma a causa del numero di sequenza e delle dimensioni, che corrisponde al numero di caratteri nella variabile di fotogramma CString . Le variabili allocate nel frame vengono deallocate automaticamente quando il frame esce dall'ambito.

Variabili Frame

In generale, non è consigliabile preoccuparsi degli oggetti heap associati alle variabili di frame perché vengono deallocati automaticamente quando le variabili del frame escono dall'ambito. Per evitare confusione nei dump di diagnostica della memoria, è necessario posizionare le chiamate a Checkpoint in modo che non si trovino nell'ambito delle variabili di frame. Ad esempio, posizionare parentesi di ambito intorno al codice di allocazione precedente, 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();

Con le parentesi di ambito in posizione, il dump della memoria per questo esempio è 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 oggetti non specifici

Si noti che alcune allocazioni sono oggetti (ad esempio CPerson) e alcune sono allocazioni non oggetto. "Allocazioni non oggetto" sono allocazioni per oggetti non derivati da CObject o allocazioni di tipi C primitivi, ad charesempio , into long. Se la classe derivata da CObject alloca spazio aggiuntivo, ad esempio per i buffer interni, tali oggetti visualizzeranno allocazioni di oggetti e non oggetti.

Prevenzione delle perdite di memoria

Si noti nel codice precedente che il blocco di memoria associato alla CString variabile frame è stato deallocato automaticamente e non viene visualizzato come perdita di memoria. La deallocazione automatica associata alle regole di ambito evita la maggior parte delle perdite di memoria associate alle variabili di contesto.

Per gli oggetti allocati nell'heap, tuttavia, è necessario eliminare in modo esplicito l'oggetto per evitare una perdita di memoria. Per pulire l'ultima perdita di memoria nell'esempio precedente, eliminare l'oggetto CPerson allocato nell'heap, come indicato di seguito:

{
    // 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 dei dump degli oggetti

Quando si deriva una classe da CObject, è possibile eseguire l'override della Dump funzione membro per fornire informazioni aggiuntive quando si usa DumpAllObjectsSince per eseguire il dump degli oggetti nella finestra Output.

La Dump funzione scrive una rappresentazione testuale delle variabili membro dell'oggetto in un contesto di dump (CDumpContext). Il contesto del dump è simile a un flusso di I/O. È possibile usare l'operatore append (<<) per inviare dati a un oggetto CDumpContext.

Quando si esegue l'override della Dump funzione, è necessario chiamare prima di tutto la versione della classe di base di Dump per eseguire il dump del contenuto dell'oggetto classe di base. Restituire quindi una descrizione testuale e un valore per ogni variabile membro della classe derivata.

La dichiarazione della Dump funzione è simile alla seguente:

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

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

Poiché il dump di oggetti ha senso solo quando si esegue il debug del programma, la dichiarazione della Dump funzione è delimitata da un blocco di codice #ifdef _DEBUG / #endif.

Nell'esempio seguente, la funzione Dump chiama prima la funzione Dump della propria classe di base. Scrive quindi una breve descrizione di ogni variabile membro insieme al valore del membro nel flusso di diagnostica.

#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 indicare dove inviare l'output del dump. La versione Debug di MFC fornisce un oggetto predefinito CDumpContext 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 compilazione di debug MFC

Le informazioni di debug per un'applicazione MFC di grandi dimensioni possono richiedere molto spazio su disco. È possibile usare una di queste procedure per ridurre le dimensioni:

  1. Ricompilare le librerie MFC usando l'opzione /Z7, /Zi, /ZI (Debug Information Format) anziché /Z7. Queste opzioni creano un singolo file PDB (Program Database) che contiene informazioni di debug per l'intera libreria, riducendo la ridondanza e risparmiando spazio.

  2. Ricompilare le librerie MFC senza informazioni di debug (nessuna opzione /Z7, /Zi, /ZI (Formato informazioni di debug). In questo caso, la mancanza di informazioni di debug impedirà di usare la maggior parte delle funzionalità del debugger all'interno del codice della libreria MFC, ma poiché le librerie MFC sono già sottoposto a debug approfondito, questo potrebbe non essere un problema.

  3. Creare un'applicazione personalizzata 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 usare l'esecuzione passo-passo e le altre funzionalità di debug in tali moduli. 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).

  1. In Esplora soluzioni selezionare il progetto .

  2. Scegliere Pagine delle proprietà dal menu Visualizza.

  3. Prima di tutto, si creerà una nuova configurazione del progetto.

    1. Nella finestra di dialogo Pagine delle proprietà del progetto> fare clic sul pulsante Configuration Manager.<

    2. Nella finestra di dialogo Configuration Manager individuare il progetto nella griglia. Nella colonna Configurazione selezionare <Nuovo.>

    3. Nella finestra di dialogo Nuova configurazione progetto digitare un nome per la nuova configurazione, ad esempio "Debug parziale", nella casella Nome configurazione progetto .

    4. Nell'elenco Copia impostazioni scegliere Rilascio.

    5. Fare clic su OK per chiudere la finestra di dialogo Nuova configurazione progetto .

    6. Chiudere la finestra di dialogo Configuration Manager .

  4. Ora verranno impostate le opzioni per l'intero progetto.

    1. Nella finestra di dialogo Pagine delle proprietà selezionare la categoria Generale nella cartella Proprietà di configurazione.

    2. Nella griglia delle impostazioni del progetto espandere Impostazioni predefinite progetto (se necessario).

    3. In Impostazioni predefinite progetto trovare Uso di MFC. L'impostazione corrente viene visualizzata nella colonna destra della griglia. Fare clic sull'impostazione corrente e modificarla in Usa MFC in una libreria statica.

    4. Nel riquadro sinistro della finestra di dialogo Pagine proprietà aprire la cartella C/C++ e selezionare Preprocessore. Nella griglia delle proprietà trovare Definizioni del preprocessore e sostituire "NDEBUG" con "_DEBUG".

    5. Nel riquadro sinistro della finestra di dialogo Pagine proprietà aprire la cartella Linker e selezionare La categoria di input . Nella griglia delle proprietà trovare Dipendenze aggiuntive. Nell'impostazione Dipendenze aggiuntive digitare "NAFXCWD. LIB" e "LIBCMT".

    6. Fare clic su OK per salvare le nuove opzioni di compilazione e chiudere la finestra di dialogo Pagine delle proprietà .

  5. Scegliere Ricompila dal menu Compila. In questo modo vengono rimosse tutte le informazioni di debug dai moduli, ma non influisce sulla libreria MFC.

  6. 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 del debugger solo nei moduli compilati con informazioni di debug. Per ogni file di progetto in cui si desidera includere le informazioni di debug, seguire questa procedura:

    1. In Esplora soluzioni aprire la cartella File di origine che si trova nel progetto.

    2. Selezionare il file per cui si desidera impostare le informazioni di debug.

    3. Scegliere Pagine delle proprietà dal menu Visualizza.

    4. Nella finestra di dialogo Pagine delle proprietà , nella cartella Impostazioni di configurazione aprire la cartella C/C++ e quindi selezionare la categoria Generale .

    5. Nella griglia delle proprietà trovare Formato informazioni di debug.

    6. Fare clic sulle impostazioni Debug Information Format (Formato informazioni di debug) e selezionare l'opzione desiderata (in genere /ZI) per le informazioni di debug.

    7. Se si usa un'applicazione generata dalla procedura guidata dell'applicazione o si dispone di intestazioni precompilate, è necessario disattivare le intestazioni precompilate o ricompilarle prima di compilare gli altri moduli. In caso contrario, si riceverà 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).

  7. Dal menu Compila selezionare Compila per ricompilare i file di progetto non aggiornati.

    In alternativa alla tecnica descritta in questo argomento, è possibile usare un makefile esterno per definire singole opzioni per ogni file. In tal caso, per collegarsi alle librerie di debug MFC, è necessario definire il flag di _DEBUG per ogni modulo. Per usare le librerie di versione MFC, è necessario definire NDEBUG. Per ulteriori informazioni sulla scrittura di makefile esterni, consultare il Riferimento NMAKE.