Bagikan melalui


Teknik Penelusuran Kesalahan MFC

Jika Anda melakukan penelusuran kesalahan pada program MFC, teknik-teknik berikut mungkin berguna.

Dalam topik ini

AfxDebugBreak

Makro TRACE

Mendeteksi kebocoran memori di MFC

AfxDebugBreak

MFC menyediakan fungsi AfxDebugBreak khusus untuk titik henti hardcoding pada kode sumber:

AfxDebugBreak( );

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

_asm int 3

Pada platform lain, AfxDebugBreak hanya memanggil DebugBreak.

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

Dalam topik ini

Makro TRACE

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

Contoh berikut menunjukkan beberapa cara Anda dapat menggunakan makro TRACE. Seperti printf, makro TRACE dapat menghandel 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 mengenai makro TRACE, lihat Layanan Diagnostik.

Dalam topik ini

Mendeteksi kebocoran memori di MFC

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

Melacak alokasi memori

Di MFC, Anda dapat menggunakan DEBUG_NEW makro sebagai pengganti operator new untuk membantu mencari kebocoran memori. Dalam versi Debug dari program Anda, DEBUG_NEW terus memantau nama file dan nomor baris untuk setiap objek yang dialokasikan olehnya. Saat Anda mengompilasi versi Rilis dari program Anda, DEBUG_NEW menyelesaikan operasi new sederhana tanpa nama file dan informasi nomor baris. Dengan demikian, Anda tidak perlu mengorbankan kecepatan di versi Rilis dari program Anda.

Jika Anda tidak ingin menulis ulang keseluruhan program untuk digunakan DEBUG_NEW sebagai pengganti new, Anda dapat menentukan makro ini di dalam file sumber Anda:

#define new DEBUG_NEW

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

Kerangka kerja MFC versi Debug menggunakan DEBUG_NEW secara otomatis, tetapi kode Anda tidak. Jika Anda ingin memanfaatkan DEBUG_NEW, Anda harus menggunakan DEBUG_NEW secara eksplisit atau #define new seperti yang telah ditunjukkan di atas.

Dalam topik ini

Mengaktifkan diagnostik memori

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

Untuk mengaktifkan atau menonaktifkan diagnostik memori

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

    Untuk memilih fitur diagnostik memori tertentu dengan afxMemDF

  • Jika Anda menginginkan kontrol yang lebih akurat atas fitur diagnostik memori, Anda dapat memilih untuk mengaktifkan dan menonaktifkan fitur diagnostik memori individual dengan mengatur nilai variabel global MFC afxMemDF. Variabel ini dapat memiliki nilai berikut seperti yang ditentukan oleh tipe terbilang afxMemDF.

    Nilai Deskripsi
    allocMemDF Mengaktifkan pengalokasi memori diagnostik (default).
    delayFreeMemDF Menunda pengosongan memori saat sedang memanggil delete atau free sampai program keluar. Hal ini akan mengakibatkan program Anda untuk mengalokasikan jumlah memori sebanyak mungkin.
    checkAlwaysMemDF Memanggil AfxCheckMemory setiap kali memori dialokasikan atau dikosongkan.

    Nilai-nilai ini dapat digunakan secara bersamaan dengan melakukan operasi logis-OR, seperti yang ditunjukkan berikut ini:

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

    Dalam topik ini

Mengambil snapshot memori

  1. Buat objek CMemoryState dan panggil fungsi anggota CMemoryState::Checkpoint. Hal ini menghasilkan snapshot memori pertama.

  2. Setelah program Anda melakukan operasi alokasi memori dan pembatalan alokasi memorinya, buatlah objek CMemoryState lain dan panggil Checkpoint untuk objek tersebut. Hal ini akan menghasilkan snapshot kedua dari penggunaan memori.

  3. Buatlah objek CMemoryState ketiga dan panggil fungsi CMemoryState::Difference-nya anggotanya, yang memberikan argumen kepada dua objek CMemoryState sebelumnya. Jika terdapat perbedaan antara dua status memori, fungsi Difference akan mengembalikan nilai yang bukan nol. Hal ini menunjukkan bahwa beberapa blok memori belum dibatalkan alokasinya.

    Contoh berikut menunjukkan tampilan 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 dikelompokkan oleh blok #ifdef _DEBUG / #endif, sehingga hanya dikompilasikan dalam versi Debug dari program Anda.

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

    Dalam topik ini

Menampilkan statistik memori

Fungsi CMemoryState::Difference memeriksa dua objek status memori dan mendeteksi objek apa pun yang tidak dibatalkan alokasinya dari timbunan di antara status awal dan akhir. Setelah Anda mengambil snapshot memori dan membandingkannya menggunakan CMemoryState::Difference, Anda dapat memanggil CMemoryState::DumpStatistics untuk mendapatkan informasi tentang objek yang alokasinya belum dibatalkan.

Pertimbangkan contoh berikut:

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

Sampel dump dari contoh memiliki tampilan seperti berikut:

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 pembatalan alokasinya tertunda jika afxMemDF diatur ke delayFreeMemDF.

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

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

Largest number used memberikan memori maksimum yang digunakan oleh program pada setiap saat.

Total allocations memberikan jumlah total memori yang digunakan oleh program.

Dalam topik ini

Mengambil dump objek

Dalam program MFC, Anda dapat menggunakan CMemoryState::DumpAllObjectsSince untuk melakukan dump deskripsi pada semua objek pada timbunan yang alokasinya belum dibatalkan. DumpAllObjectsSince melakukan dump pada semua objek yang dialokasikan sejak CMemoryState::Checkpoint terakhir. Jika tidak ada panggilan Checkpoint yang terjadi, DumpAllObjectsSince melakukan dump pada semua objek dan non-objek yang saat ini ada dalam memori.

Catatan

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

Catatan

MFC melakukan dump secara otomatis pada semua objek yang bocor ketika program Anda keluar, sehingga Anda tidak perlu membuat kode untuk melakukan dump pada objek pada saat itu.

Kode berikut menguji kebocoran memori dengan membandingkan dua status memori dan melakukan dump pada semua objek jika terdeteksi adanya kebocoran.

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

Isi dari dump terlihat 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

{1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long

Angka dalam kurung kurawal di awal mayoritas baris menentukan urutan alokasi objek. Objek yang baru dialokasikan memiliki angka tertinggi dan muncul di bagian atas dump.

Untuk memperoleh jumlah informasi yang maksimum dari dump objek, Anda dapat mengambil alih fungsi anggota Dump dari objek turunan CObject apa pun untuk menyesuaikan dump objek.

Anda dapat mengatur titik henti pada alokasi memori tertentu dengan mengatur variabel global _afxBreakAlloc ke angka yang ditampilkan dalam kurung kurawal. Jika Anda menjalankan ulang program, debugger akan menghentikan eksekusi ketika alokasi itu berlangsung. Selanjutnya, Anda dapat melihat tumpukan panggilan untuk melihat cara program Anda mencapai titik tersebut.

Pustaka runtime C memiliki fungsi yang serupa, _CrtSetBreakAlloc, yang dapat Anda gunakan untuk alokasi runtime C.

Dalam topik ini

Menginterpretasikan dump memori

Lihatlah dump objek ini secara lebih terperinci:

{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 yang eksplisit—satu alokasi ada di tumpukan dan satu lagi ada 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" );

Konstruktor CPerson mengambil tiga argumen yang merupakan penunjuk ke char, yang digunakan untuk menginisialisasi variabel anggota CString. Dalam dump memori, Anda dapat melihat objek CPerson dan tiga blok non-objek (3, 4, dan 5). Objek ini menyimpan karakter untuk variabel anggota CString dan tidak akan dihapus ketika destruktor CPerson objek digunakan.

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

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

Variable Bingkai

Pada umumnya, Anda tidak perlu mengkhawatirkan objek timbunan yang dikaitkan dengan variabel bingkai karena secara otomatis dibatalkan alokasinya ketika variabel bingkai keluar dari cakupan. Untuk menghindari kekacauan dalam dump diagnostik memori Anda, Anda harus menempatkan panggilan Checkpoint Anda agar berada di luar cakupan variabel bingkai. Misalnya, letakkan kurung cakupan di sekitar kode alokasi sebelumnya, seperti yang ditunjukkan di bawah ini:

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

Berikut adalah contoh untuk dump memori ini yang telah dilengkapi oleh kurung cakupan:

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 Non-objek

Perhatikan bahwa beberapa alokasi adalah objek (seperti CPerson) dan beberapa lagi adalah alokasi non-objek. "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 non-objek.

Mencegah Kebocoran Memori

Perhatikan pada kode di atas bahwa blok memori yang dikaitkan dengan CString variabel bingkai telah dibatalkan alokasinya secara otomatis dan tidak muncul sebagai kebocoran memori. Pembatalan alokasi secara otomatis yang dikaitkan dengan aturan cakupan menangani mayoritas kebocoran memori yang terkait dengan variabel bingkai.

Namun, untuk objek yang dialokasikan pada timbunan, Anda harus secara eksplisit menghapus objek untuk mencegah terjadinya kebocoran memori. Untuk membersihkan kebocoran memori terakhir pada contoh sebelumnya, hapus objek CPerson yang dialokasikan pada timbunan, seperti 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;
}

Dalam topik ini

Menyesuaikan dump objek

Saat Anda memperoleh kelas dari CObject, Anda dapat mengambil alih fungsi anggota Dump untuk memberikan informasi tambahan saat Anda menggunakan DumpAllObjectsSince untuk mencadangkan objek ke Jendela output.

Fungsi Dump menulis representasi tekstual variabel anggota objek ke konteks dump (CDumpContext). Konteks dump serupa dengan aliran I/O. Anda dapat menggunakan operator tambahan (<<) untuk mengirim data ke CDumpContext.

Ketika Anda mengambil alih fungsi Dump, Anda harus memanggil versi kelas dasar terlebih dahulu dari Dump untuk melakukan dump pada konten objek kelas dasar. Kemudian, hasilkan output deskripsi tekstual dan nilai untuk setiap variabel anggota kelas turunan Anda.

Deklarasi fungsi Dump memiliki tampilan seperti berikut:

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

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

Karena dump objek hanya dirasa wajar ketika Anda melakukan debug pada program Anda, deklarasi fungsi Dump dikelompokkan dengan blok #ifdef _DEBUG / #endif.

Dalam contoh berikut, fungsi Dump memanggil fungsi Dump terlebih dahulu untuk kelas dasarnya. Fungsi itu lalu menulis deskripsi singkat dari setiap variabel anggota dan nilai anggota ke aliran diagnostik.

#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 argumen CDumpContext untuk menentukan lokasi target output dump. Versi Debug dari MFC menyediakan objek yang telah ditentukan CDumpContext sebelumnya bernama afxDump yang mengirimkan output ke debugger.

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

Dalam topik ini

Mengurangi ukuran build Debug MFC

Informasi debug untuk aplikasi MFC berukuran besar dapat memakan banyak ruang penyimpanan pada disk. Anda dapat menggunakan salah satu dari prosedur berikut untuk mengurangi ukurannya:

  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, sehingga mengurangi redundansi dan menghemat ruang penyimpanan.

  2. Bangun ulang pustaka MFC tanpa informasi debug (tanpa opsi /Z7, /Zi, /ZI (Format Informasi Debug )). Dalam hal ini, kurangnya informasi debug akan mencegah Anda menggunakan mayoritas fasilitas debugger dalam kode pustaka MFC, tetapi karena pustaka MFC sudah melalui proses debug secara menyeluruh, hal ini mungkin tidak akan menjadi masalah.

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

    Dalam topik ini

Membangun aplikasi MFC dengan informasi debug untuk modul yang dipilih

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

  1. Di Penjelajah Solusi, pilih proyek.

  2. Dari menu Tampilan, pilih Halaman Properti.

  3. Pertama-tama, Anda akan membuat konfigurasi proyek baru.

    1. Di kotak dialog <Proyek> Halaman Properti, klik tombol Pengelola Konfigurasi.

    2. Dalam kotak dialog Pengelola Konfigurasi, temukan proyek Anda di dalam 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 daftar Salin Pengaturan dari, pilih Rilis.

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

    6. Tutup kotak dialog Pengelola Konfigurasi.

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

    1. Di kotak dialog Halaman Properti, pada folder Properti Konfigurasi, pilih kategori Umum.

    2. Di kisi pengaturan proyek, luaskan Default Proyek (jika perlu).

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

    4. Di panel kiri kotak dialog Halaman Properti, buka folder C/C++ dan pilih Praprosesor. 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. Pada kisi properti, temukan Dependensi Tambahan. Dalam pengaturan Dependensi Tambahan, ketik "NAFXCWD. LIB" dan "LIBCMT."

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

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

  6. Sekarang Anda harus menambahkan kembali informasi debug ke modul yang dipilih di aplikasi Anda. Perlu diingat bahwa Anda dapat mengatur titik henti dan melakukan fungsi debugger lainnya hanya dalam modul yang telah dikompilasi 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 berada di bawah proyek Anda.

    2. Pilih file yang informasi debugnya ingin Anda atur.

    3. Dari menu Tampilan, pilih Halaman Properti.

    4. Di kotak dialog Halaman Properti, pada folder Konfigurasi Pengaturan, buka folder C/C++ lalu pilih kategori Umum.

    5. Pada kisi properti, temukan Format Informasi Debug.

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

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

  7. Dari menu Build, pilih Bangun untuk membangun ulang file proyek yang telah kedaluarsa.

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

    Dalam topik ini