Bagikan melalui


Teknik Debugging MFC

Jika Anda melakukan debugging program MFC, teknik penelusuran kesalahan ini mungkin berguna.

AfxDebugBreak

MFC menyediakan fungsi AfxDebugBreak khusus untuk titik henti hard-coding dalam kode sumber:

AfxDebugBreak( );

Pada platform Intel, AfxDebugBreak menghasilkan kode berikut, yang memecah kode sumber daripada kode kernel:

_asm int 3

Pada platform lain, AfxDebugBreak hanya memanggil DebugBreak.

Pastikan untuk menghapus AfxDebugBreak pernyataan ketika Anda membuat build rilis atau gunakan #ifdef _DEBUG untuk mengelilinginya.

Makro TRACE

Untuk menampilkan pesan dari program Anda di jendela Output debugger, Anda dapat menggunakan makro ATLTRACE atau makro MFC TRACE . Seperti pernyataan, makro pelacakan hanya aktif dalam versi Debug program Anda dan menghilang saat dikompilasi dalam versi Rilis.

Contoh berikut menunjukkan beberapa cara Anda dapat menggunakan makro TRACE . Seperti printf, makro TRACE dapat menangani sejumlah argumen.

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

Makro TRACE menangani parameter char* dan wchar_t* dengan tepat. Contoh berikut menunjukkan penggunaan makro TRACE bersama dengan berbagai jenis parameter string.

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

Untuk informasi selengkapnya tentang makro TRACE , lihat Layanan Diagnostik.

Mendeteksi kebocoran memori di MFC

MFC menyediakan kelas dan fungsi untuk mendeteksi memori yang dialokasikan tetapi tidak pernah dibatalkan alokasinya.

Melacak alokasi memori

Di MFC, Anda dapat menggunakan DEBUG_NEW makro sebagai pengganti operator baru untuk membantu menemukan kebocoran memori. Dalam versi Debug program Anda, DEBUG_NEW melacak nama file dan nomor baris untuk setiap objek yang dialokasikannya. Saat Anda mengkompilasi versi Rilis program Anda, DEBUG_NEW menyelesaikan operasi baru sederhana tanpa nama file dan informasi nomor baris. Dengan demikian, Anda tidak membayar penalti kecepatan dalam versi Rilis program Anda.

Jika Anda tidak ingin menulis ulang seluruh program untuk digunakan DEBUG_NEW sebagai pengganti yang baru, Anda dapat menentukan makro ini dalam file sumber Anda:

#define new DEBUG_NEW

Ketika Anda melakukan pencadangan objek, setiap objek yang dialokasikan dengan DEBUG_NEW akan menampilkan file dan nomor baris tempat objek dialokasikan, memungkinkan Anda untuk menentukan sumber kebocoran memori.

Versi Debug kerangka kerja MFC menggunakan DEBUG_NEW secara otomatis, tetapi kode Anda tidak. Jika Anda menginginkan manfaat dari DEBUG_NEW, Anda harus menggunakan DEBUG_NEW secara eksplisit atau #define new seperti yang telah ditunjukkan di atas.

Mengaktifkan diagnostik memori

Sebelum dapat menggunakan fasilitas diagnostik memori, Anda harus mengaktifkan pelacakan diagnostik.

Untuk mengaktifkan atau menonaktifkan diagnostik memori

  • Panggil fungsi global AfxEnableMemoryTracking untuk mengaktifkan atau menonaktifkan alokator memori diagnostik. Karena diagnostik memori aktif secara default di pustaka debug, Anda umumnya akan menggunakan fungsi ini untuk mematikannya sementara, yang meningkatkan kecepatan eksekusi program dan mengurangi output diagnostik.

    Untuk memilih fitur diagnostik memori tertentu dengan afxMemDF

  • Jika Anda menginginkan kontrol yang lebih tepat atas fitur diagnostik memori, Anda dapat secara selektif mengaktifkan dan menonaktifkan fitur diagnostik memori individual dengan mengatur nilai variabel global MFC afxMemDF. Variabel ini dapat memiliki nilai berikut seperti yang ditentukan oleh jenis enumerasi afxMemDF.

    Nilai Deskripsi
    allocMemDF Aktifkan alokator memori diagnostik (default).
    penundaanMemDF Bebas Tunda membebaskan memori ketika memanggil delete atau free sampai program keluar. Ini akan menyebabkan program Anda mengalokasikan jumlah memori maksimum yang mungkin.
    periksaSelaluMemDF Panggil AfxCheckMemory setiap kali memori dialokasikan atau dibebaskan.

    Nilai-nilai ini dapat digunakan dalam kombinasi dengan melakukan operasi logis-OR, seperti yang ditunjukkan di sini:

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

Mengambil snapshot memori

  1. Buat objek CMemoryState dan panggil fungsi anggota CMemoryState::Checkpoint . Ini membuat cuplikan memori pertama.

  2. Setelah program Anda melakukan operasi alokasi dan dealokasi memorinya, buat objek lain CMemoryState dan panggil Checkpoint objek tersebut. Ini mendapatkan rekam jepret kedua penggunaan memori.

  3. Buat objek ketiga CMemoryState dan panggil fungsi anggota CMemoryState::Difference-nya, dengan memberikan sebagai argumen dua objek sebelumnya CMemoryState. Jika ada perbedaan antara dua status memori, Difference fungsi mengembalikan nilai bukan nol. Ini menunjukkan bahwa beberapa blok memori belum dialokasikan kembali.

    Contoh ini menunjukkan seperti apa kode tersebut:

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

    Perhatikan bahwa pernyataan pemeriksaan memori dikurung oleh blok #ifdef _DEBUG / #endif sehingga hanya dikompilasi dalam versi Debug program Anda.

    Sekarang setelah Anda tahu ada kebocoran memori, Anda dapat menggunakan fungsi anggota lain, CMemoryState::D umpStatistics yang akan membantu Anda menemukannya.

Melihat statistik memori

Fungsi CMemoryState::Difference melihat dua objek status memori dan mendeteksi objek apa pun yang tidak didealokasi dari heap antara status awal dan akhir. Setelah Anda mengambil rekam jepret memori dan membandingkannya menggunakan CMemoryState::Difference, Anda dapat memanggil CMemoryState::DumpStatistics untuk mendapatkan informasi tentang objek yang belum dilepaskan alokasinya.

Pertimbangkan contoh berikut:

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

Sampel cadangan dari contoh terlihat seperti ini:

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

Blok bebas adalah blok yang pembebasannya tertunda jika afxMemDF diatur ke delayFreeMemDF.

Blok objek biasa, yang ditunjukkan pada baris kedua, tetap dialokasikan pada timbunan.

Blok non-objek mencakup array dan struktur yang dialokasikan dengan new. Dalam hal ini, empat blok non-objek dialokasikan pada timbunan tetapi tidak dibatalkan alokasinya.

Largest number used memberikan memori maksimum yang digunakan oleh program kapan saja.

Total allocations memberikan jumlah total memori yang digunakan oleh program.

Mengambil cuplikan objek

Dalam program MFC, Anda dapat menggunakan CMemoryState::DumpAllObjectsSince untuk mencetak deskripsi semua objek pada heap yang belum dibatalkan alokasinya. DumpAllObjectsSince menampilkan semua objek yang dialokasikan sejak CMemoryState::Checkpoint terakhir. Jika tidak ada Checkpoint panggilan yang terjadi, DumpAllObjectsSince membuang semua objek dan non-objek yang ada di dalam memori.

Nota

Sebelum dapat menggunakan pembuangan objek MFC, Anda harus mengaktifkan pelacakan diagnostik.

Nota

MFC secara otomatis membuang semua objek yang bocor saat program Anda menutup, sehingga Anda tidak perlu membuat kode untuk membuang informasi objek pada saat itu.

Kode berikut menguji kebocoran memori dengan membandingkan dua status memori dan membuang semua objek jika kebocoran terdeteksi.

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

Isi dari dump data terlihat seperti ini:

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

Angka dalam kurung kurawal di awal sebagian besar baris menentukan urutan tempat objek dialokasikan. Objek yang terakhir dialokasikan memiliki nomor terbesar dan muncul di bagian atas tumpukan.

Untuk mendapatkan informasi sebanyak mungkin dari pembuangan objek, Anda dapat mengganti Dump fungsi anggota dari objek yang diturunkan dari CObject untuk menyesuaikan pembuangan objek.

Anda dapat mengatur titik henti pada alokasi memori tertentu dengan mengatur variabel _afxBreakAlloc global ke angka yang ditunjukkan dalam kurung kurawal. Jika Anda menjalankan ulang program, debugger akan memutuskan eksekusi ketika alokasi tersebut berlangsung. Anda kemudian dapat melihat tumpukan panggilan untuk melihat bagaimana program Anda sampai ke titik itu.

Pustaka run-time C memiliki fungsi serupa, _CrtSetBreakAlloc, yang dapat Anda gunakan untuk alokasi run-time C.

Menginterpretasikan cadangan memori

Lihat dump objek ini secara lebih 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

Program yang menghasilkan dump ini hanya memiliki dua alokasi eksplisit—satu di tumpukan dan satu di timbunan.

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

CPerson Konstruktor mengambil tiga argumen yang merupakan pointer ke char, yang digunakan untuk menginisialisasi anggota variabel CString. Dalam cadangan memori, Anda dapat melihat CPerson objek bersama dengan tiga blok nonobjek (3, 4, dan 5). Ini menyimpan karakter untuk CString variabel anggota dan tidak akan dihapus saat CPerson destruktor objek dipanggil.

Blok nomor 2 adalah objek itu CPerson sendiri. $51A4 mewakili alamat blok dan diikuti oleh konten objek, yang dihasilkan oleh CPerson::Dump ketika dipanggil oleh DumpAllObjectsSince.

Anda dapat menebak bahwa blok nomor 1 dikaitkan dengan variabel bingkai CString karena nomor urutan dan ukurannya, yang cocok dengan jumlah karakter dalam variabel bingkai CString. Variabel yang dialokasikan pada bingkai secara otomatis dibatalkan alokasinya ketika bingkai keluar dari cakupan.

Variabel Bingkai

Secara umum, Anda tidak perlu khawatir tentang objek tumpukan yang terkait dengan variabel bingkai karena secara otomatis dibatalkan alokasinya ketika variabel bingkai keluar dari cakupan. Untuk menghindari kekacauan dalam cadangan diagnostik memori Anda, Anda harus memposisikan panggilan Anda ke Checkpoint sehingga berada di luar cakupan variabel frame. Misalnya, tempatkan tanda kurung lingkup di sekitar kode alokasi sebelumnya, seperti yang ditunjukkan di sini:

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

Dengan tanda kurung lingkup sudah terpasang, cadangan memori untuk contoh ini adalah sebagai berikut:

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

Alokasi Bukan Objek

Perhatikan bahwa beberapa alokasi adalah objek (seperti CPerson) dan beberapa adalah alokasi nonobjek. "Alokasi non-objek" adalah alokasi untuk objek yang tidak berasal dari CObject atau alokasi jenis C primitif seperti char, int, atau long. Jika kelas turunan CObject mengalokasikan ruang tambahan, seperti untuk buffer internal, objek tersebut akan menampilkan alokasi objek dan nonobject.

Mencegah Kebocoran Memori

Perhatikan dalam kode di atas bahwa blok memori yang terkait dengan CString variabel bingkai telah dibatalkan alokasinya secara otomatis dan tidak muncul sebagai kebocoran memori. Dealokasi otomatis yang terkait dengan aturan lingkup mengurus sebagian besar kebocoran memori yang terkait dengan variabel frame.

Namun, untuk objek yang dialokasikan pada timbunan, Anda harus secara eksplisit menghapus objek untuk mencegah kebocoran memori. Untuk membersihkan kebocoran memori terakhir dalam contoh sebelumnya, hapus objek yang CPerson dialokasikan pada tumpukan, sebagai berikut:

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

Menyesuaikan pembuangan objek

Saat Anda mendapatkan kelas dari CObject, Anda dapat mengambil Dump alih fungsi anggota untuk memberikan informasi tambahan saat Anda menggunakan DumpAllObjectsSince untuk membuang objek ke jendela Output.

Fungsi ini Dump menulis representasi tekstual variabel anggota objek ke konteks cadangan (CDumpContext). Konteks pembongkaran mirip dengan aliran input/output. Anda dapat menggunakan operator tambahan (<<) untuk mengirim data ke CDumpContext.

Saat Anda menimpa fungsi Dump, Anda harus terlebih dahulu memanggil versi Dump dari kelas dasar untuk mengeluarkan isi objek kelas dasar. Kemudian keluarkan deskripsi dan nilai tekstual untuk setiap variabel anggota kelas turunan Anda.

Deklarasi Dump fungsi terlihat seperti ini:

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

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

Karena pencetakan objek hanya masuk akal ketika Anda men-debug program Anda, deklarasi fungsi Dump dikurung dengan blok #ifdef _DEBUG / #endif.

Dalam contoh berikut, fungsi Dump pertama-tama memanggil fungsi Dump untuk kelas dasarnya. Kemudian menuliskan deskripsi singkat untuk setiap variabel anggota beserta nilai dari anggota tersebut ke dalam aliran diagnosis.

#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

Anda harus menyediakan CDumpContext argumen untuk menentukan ke mana keluaran dump akan pergi. Versi Debug MFC memasok objek yang telah CDumpContext ditentukan sebelumnya bernama afxDump yang mengirim output ke debugger.

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

Mengurangi ukuran file MFC Debug build

Informasi debug untuk aplikasi MFC besar dapat memakan banyak ruang disk. Anda dapat menggunakan salah satu prosedur ini untuk mengurangi ukuran:

  1. Bangun ulang pustaka MFC menggunakan opsi /Z7, /Zi, /ZI (Format Informasi Debug), bukan /Z7. Opsi ini membangun file database program tunggal (PDB) yang berisi informasi debug untuk seluruh pustaka, mengurangi redundansi dan menghemat ruang.

  2. Membangun kembali perpustakaan MFC tanpa informasi debug (tidak ada opsi /Z7, /Zi, /ZI (Format Informasi Debug)). Dalam hal ini, kurangnya informasi debug akan mencegah Anda menggunakan sebagian besar fasilitas debugger dalam kode pustaka MFC, tetapi karena pustaka MFC sudah di-debug secara menyeluruh, ini mungkin bukan masalah.

  3. Buat aplikasi Anda sendiri dengan informasi debug untuk modul yang dipilih hanya seperti yang dijelaskan di bawah ini.

Membangun aplikasi MFC dengan informasi debug untuk modul yang dipilih

Membangun modul yang dipilih dengan pustaka debug MFC memungkinkan Anda menggunakan langkah demi langkah dan fasilitas debug lainnya dalam modul tersebut. Prosedur ini menggunakan konfigurasi Debug dan Rilis proyek, sehingga mengharuskan perubahan yang dijelaskan dalam langkah-langkah berikut (dan juga membuat "membangun ulang semua" yang diperlukan ketika build Rilis lengkap diperlukan).

  1. Di Penjelajah Solusi, pilih proyek.

  2. Dari menu Tampilan , pilih Halaman Properti.

  3. Pertama, Anda akan membuat konfigurasi proyek baru.

    1. Dalam kotak <dialog Halaman Properti Proyek>, klik tombol Manajer Konfigurasi.

    2. Dalam kotak dialog Configuration Manager, temukan proyek Anda di kisi. Di kolom Konfigurasi , pilih <Baru...>.

    3. Dalam kotak dialog Konfigurasi Proyek Baru, ketik nama untuk konfigurasi baru Anda, seperti "Debug Parsial", dalam kotak Nama Konfigurasi Proyek .

    4. Di dalam daftar Salin Pengaturan dari, pilih Rilis.

    5. Klik OK untuk menutup kotak dialog Konfigurasi Proyek Baru .

    6. Tutup kotak dialog Configuration Manager .

  4. Sekarang, Anda akan mengatur opsi untuk seluruh proyek.

    1. Dalam kotak dialog Halaman Properti , di bawah folder Properti Konfigurasi , pilih kategori Umum .

    2. Di kisi pengaturan proyek, perluas Pengaturan Bawaan Proyek (jika perlu).

    3. Di bawah Default Proyek, temukan Penggunaan MFC. Pengaturan saat ini muncul di kolom kanan kisi. Klik pengaturan saat ini dan ubah ke Gunakan MFC di Pustaka Statis.

    4. Di panel kiri kotak dialog Halaman Properti , buka folder C/C++ dan pilih Preproscessor. Di kisi properti, temukan Definisi Praprosesor dan ganti "NDEBUG" dengan "_DEBUG".

    5. Di panel kiri kotak dialog Halaman Properti , buka folder Linker dan pilih Kategori Input . Di grid properti, temukan Dependensi Tambahan. Dalam pengaturan Dependensi Tambahan , ketik "NAFXCWD. LIB" dan "LIBCMT."

    6. Klik OK untuk menyimpan opsi build baru dan menutup kotak dialog Halaman Properti .

  5. Dari menu Build , pilih Bangun ulang. Ini menghapus semua informasi debug dari modul Anda tetapi tidak memengaruhi pustaka MFC.

  6. Sekarang Anda harus menambahkan informasi debug kembali ke modul yang dipilih di aplikasi Anda. Ingatlah bahwa Anda dapat mengatur titik henti dan melakukan fungsi debugger lainnya hanya dalam modul yang telah Anda kompilasi dengan informasi debug. Untuk setiap file proyek tempat Anda ingin menyertakan informasi debug, lakukan langkah-langkah berikut:

    1. Di Penjelajah Solusi, buka folder File Sumber yang terletak di bawah proyek Anda.

    2. Pilih file yang ingin Anda atur informasi debugnya.

    3. Dari menu Tampilan , pilih Halaman Properti.

    4. Dalam kotak dialog Halaman Properti , di bawah folder Pengaturan Konfigurasi , buka folder C/C++ lalu pilih kategori Umum .

    5. Di tabel properti, temukan Format Informasi Debug.

    6. Klik pengaturan Format Informasi Debug dan pilih opsi yang diinginkan (biasanya /ZI) untuk informasi debug.

    7. Jika Anda menggunakan aplikasi yang dihasilkan oleh wizard aplikasi atau memiliki header yang telah dikompilasi sebelumnya, Anda harus menonaktifkan header yang telah dikompilasi sebelumnya atau mengompilasinya ulang sebelum mengompilasi modul lain. Jika tidak, Anda akan menerima peringatan C4650 dan pesan kesalahan C2855. Anda dapat menonaktifkan header yang telah dikompilasi sebelumnya dengan mengubah pengaturan Buat/Gunakan Header yang Telah Dikompilasi sebelumnya dalam <kotak dialog Properti Proyek> (folder Properti Konfigurasi, subfolder C/C++, Kategori Header yang Telah Dikompilasi sebelumnya).

  7. Dari menu Build , pilih Build untuk membangun kembali file proyek yang sudah kedaluarsa.

    Sebagai alternatif untuk teknik yang dijelaskan dalam topik ini, Anda dapat menggunakan makefile eksternal untuk menentukan opsi individual untuk setiap file. Dalam hal ini, untuk menautkan dengan pustaka debug MFC, Anda harus menentukan bendera _DEBUG untuk setiap modul. Jika Anda ingin menggunakan pustaka rilis MFC, Anda harus menentukan NDEBUG. Untuk informasi lebih lanjut tentang menulis makefile eksternal, lihat Referensi NMAKE.