Detail timbunan debug CRT

Tumpukan debug CRT dan fungsi terkait menyediakan banyak cara untuk melacak dan men-debug masalah manajemen memori dalam kode Anda. Anda dapat menggunakannya untuk menemukan overrun buffer, dan untuk melacak dan melaporkan alokasi memori dan status memori. Ini juga memiliki dukungan untuk membuat fungsi alokasi debug Anda sendiri untuk kebutuhan aplikasi unik Anda.

Menemukan buffer overruns dengan tumpukan debug

Dua masalah paling umum dan intrakbel yang dihadapi programmer menimpa akhir kebocoran buffer dan memori yang dialokasikan (gagal membebaskan alokasi setelah tidak lagi diperlukan). Timbunan debug menyediakan alat yang kuat untuk membantu menyelesaikan masalah alokasi memori.

Versi Debug dari fungsi heap memanggil versi standar atau dasar yang digunakan dalam build Rilis. Saat Anda meminta blok memori, manajer timbunan debug mengalokasikan dari tumpukan dasar blok memori yang sedikit lebih besar dari yang Anda minta dan mengembalikan pointer ke bagian Anda dari blok tersebut. Misalnya, aplikasi Anda berisi panggilan: malloc( 10 ). Dalam build Rilis, malloc akan memanggil rutinitas alokasi timbunan dasar yang meminta alokasi 10 byte. Namun, dalam build Debug, malloc akan memanggil _malloc_dbg, yang kemudian akan memanggil rutinitas alokasi timbunan dasar yang meminta alokasi 10 byte ditambah sekitar 36 byte memori tambahan. Semua blok memori yang dihasilkan dalam tumpukan debug terhubung dalam satu daftar tertaut, diurutkan sesuai dengan kapan mereka dialokasikan.

Memori tambahan yang dialokasikan oleh rutinitas tumpukan debug digunakan untuk informasi pembukuan. Ini memiliki pointer yang menautkan blok memori debug bersama-sama, dan buffer kecil di kedua sisi data Anda untuk menangkap timpa wilayah yang dialokasikan.

Saat ini, struktur header blok yang digunakan untuk menyimpan informasi pembukuan tumpukan debug dideklarasikan di <crtdbg.h> header dan ditentukan dalam <debug_heap.cpp> file sumber CRT. Secara konseptual, ini mirip dengan struktur ini:

typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
    _CrtMemBlockHeader* _block_header_next;
// Pointer to the block allocated just after this one:
    _CrtMemBlockHeader* _block_header_prev;
    char const*         _file_name;
    int                 _line_number;

    int                 _block_use;      // Type of block
    size_t              _data_size;      // Size of user block

    long                _request_number; // Allocation number
// Buffer just before (lower than) the user's memory:
    unsigned char       _gap[no_mans_land_size];

    // Followed by:
    // unsigned char    _data[_data_size];
    // unsigned char    _another_gap[no_mans_land_size];
} _CrtMemBlockHeader;

Buffer no_mans_land di salah satu sisi area data pengguna blok saat ini berukuran 4 byte, dan diisi dengan nilai byte yang diketahui yang digunakan oleh rutinitas tumpukan debug untuk memverifikasi bahwa batas blok memori pengguna belum ditimpa. Tumpukan debug juga mengisi blok memori baru dengan nilai yang diketahui. Jika Anda memilih untuk menyimpan blok yang dibeberkan dalam daftar tertaut timbunan, blok yang dibeberkan ini juga diisi dengan nilai yang diketahui. Saat ini, nilai byte aktual yang digunakan adalah sebagai berikut:

no_mans_land (0xFD)
Buffer "no_mans_land" di salah satu sisi memori yang digunakan oleh aplikasi saat ini diisi dengan 0xFD.

Blok yang dibekukan (0xDD)
Blok yang dibeberkan tetap tidak digunakan dalam daftar tertaut timbunan debug saat _CRTDBG_DELAY_FREE_MEM_DF bendera diatur saat ini diisi dengan 0xDD.

Objek baru (0xCD)
Objek baru diisi dengan 0xCD saat dialokasikan.

Jenis blok pada tumpukan debug

Setiap blok memori dalam tumpukan debug ditetapkan ke salah satu dari lima jenis alokasi. Jenis-jenis ini dilacak dan dilaporkan secara berbeda untuk tujuan deteksi kebocoran dan pelaporan status. Anda dapat menentukan jenis blok dengan mengalokasikannya menggunakan panggilan langsung ke salah satu fungsi alokasi timbunan debug seperti _malloc_dbg. Lima jenis blok memori dalam tumpukan debug (diatur dalam nBlockUse anggota _CrtMemBlockHeader struktur) adalah sebagai berikut:

_NORMAL_BLOCK
Panggilan ke malloc atau calloc membuat blok Normal. Jika Anda hanya ingin menggunakan blok Normal, dan tidak memerlukan blok Klien, Anda mungkin ingin menentukan _CRTDBG_MAP_ALLOC. _CRTDBG_MAP_ALLOC menyebabkan semua panggilan alokasi timbunan dipetakan ke setara debug mereka dalam build Debug. Ini memungkinkan penyimpanan nama file dan informasi nomor baris tentang setiap panggilan alokasi di header blok yang sesuai.

_CRT_BLOCK
Blok memori yang dialokasikan secara internal oleh banyak fungsi pustaka run-time ditandai sebagai blok CRT sehingga dapat ditangani secara terpisah. Akibatnya, deteksi kebocoran dan operasi lainnya mungkin tetap tidak terpengaruh oleh mereka. Alokasi tidak boleh mengalokasikan, mengalokasikan ulang, atau membebaskan blok jenis CRT apa pun.

_CLIENT_BLOCK
Aplikasi dapat melacak grup alokasi tertentu untuk tujuan penelusuran kesalahan dengan mengalokasikannya sebagai jenis blok memori ini, menggunakan panggilan eksplisit ke fungsi timbunan debug. MFC, misalnya, mengalokasikan semua CObject objek sebagai blok Klien; aplikasi lain mungkin menyimpan objek memori yang berbeda di blok Klien. Subjenis blok Klien juga dapat ditentukan untuk granularitas pelacakan yang lebih besar. Untuk menentukan subjenis blok Klien, geser angka ke kiri 16 bit dan OR dengan _CLIENT_BLOCK. Contohnya:

#define MYSUBTYPE 4
freedbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16));

Fungsi hook yang disediakan klien untuk mencadangkan objek yang disimpan di blok Klien dapat diinstal menggunakan _CrtSetDumpClient, dan kemudian akan dipanggil setiap kali blok Klien dibuang oleh fungsi debug. Selain itu, _CrtDoForAllClientObjects dapat digunakan untuk memanggil fungsi tertentu yang disediakan oleh aplikasi untuk setiap blok Klien dalam tumpukan debug.

_FREE_BLOCK
Biasanya, blok yang dibesarkan dihapus dari daftar. Untuk memeriksa bahwa memori yang dibebaskan tidak ditulis ke, atau untuk mensimulasikan kondisi memori rendah, Anda dapat menyimpan blok yang dibebaskan pada daftar tertaut, ditandai sebagai Gratis, dan diisi dengan nilai byte yang diketahui (saat ini 0xDD).

_IGNORE_BLOCK
Dimungkinkan untuk menonaktifkan operasi timbunan debug untuk beberapa interval. Selama waktu ini, blok memori disimpan dalam daftar, tetapi ditandai sebagai Blok abaikan.

Untuk menentukan jenis dan subjenis _BLOCK_TYPE blok tertentu, gunakan fungsi _CrtReportBlockType dan makro dan _BLOCK_SUBTYPE. Makro didefinisikan sebagai <crtdbg.h> berikut:

#define _BLOCK_TYPE(block)          (block & 0xFFFF)
#define _BLOCK_SUBTYPE(block)       (block >> 16 & 0xFFFF)

Periksa integritas tumpukan dan kebocoran memori

Banyak fitur tumpukan debug harus diakses dari dalam kode Anda. Bagian berikut menjelaskan beberapa fitur dan cara menggunakannya.

_CrtCheckMemory
Anda dapat menggunakan panggilan ke _CrtCheckMemory, misalnya, untuk memeriksa integritas tumpukan kapan saja. Fungsi ini memeriksa setiap blok memori dalam timbunan. Ini memverifikasi bahwa informasi header blok memori valid, dan mengonfirmasi bahwa buffer belum dimodifikasi.

_CrtSetDbgFlag
Anda dapat mengontrol bagaimana timbunan debug melacak alokasi menggunakan bendera internal, _crtDbgFlag, yang dapat dibaca dan diatur menggunakan _CrtSetDbgFlag fungsi . Dengan mengubah bendera ini, Anda dapat menginstruksikan timbunan debug untuk memeriksa kebocoran memori ketika program keluar dan melaporkan kebocoran yang terdeteksi. Demikian pula, Anda dapat memberi tahu tumpukan untuk meninggalkan blok memori yang dibeberkan dalam daftar yang ditautkan, untuk mensimulasikan situasi memori rendah. Ketika tumpukan diperiksa, blok yang dibebaskan ini diperiksa secara keseluruhan untuk memastikan bahwa mereka belum terganggu.

Bendera _crtDbgFlag berisi bidang bit berikut:

Bidang bit Nilai default Deskripsi
_CRTDBG_ALLOC_MEM_DF Aktif Mengaktifkan alokasi debug. Ketika bit ini nonaktif, alokasi tetap ditautkan bersama-sama, tetapi jenis bloknya adalah _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF Nonaktif Mencegah memori benar-benar dibebaskan, seperti untuk menyimulasikan kondisi memori rendah. Ketika bit ini aktif, blok yang dibebaskan disimpan dalam daftar tertaut timbunan debug tetapi ditandai sebagai _FREE_BLOCK dan diisi dengan nilai byte khusus.
_CRTDBG_CHECK_ALWAYS_DF Nonaktif _CrtCheckMemory Penyebab dipanggil di setiap alokasi dan alokasi. Eksekusi lebih lambat, tetapi menangkap kesalahan dengan cepat.
_CRTDBG_CHECK_CRT_DF Nonaktif Menyebabkan blok yang ditandai sebagai jenis _CRT_BLOCK disertakan dalam operasi deteksi kebocoran dan perbedaan status. Ketika bit ini nonaktif, memori yang digunakan secara internal oleh pustaka run-time diabaikan selama operasi tersebut.
_CRTDBG_LEAK_CHECK_DF Nonaktif Penyebab pemeriksaan kebocoran dilakukan di pintu keluar program melalui panggilan ke _CrtDumpMemoryLeaks. Laporan kesalahan dihasilkan jika aplikasi gagal membebaskan semua memori yang dialokasikannya.

Mengonfigurasi tumpukan debug

Semua panggilan ke fungsi heap seperti malloc, free, calloc, realloc, new, dan delete menyelesaikan untuk men-debug versi fungsi yang beroperasi dalam tumpukan debug. Ketika Anda membebaskan blok memori, tumpukan debug secara otomatis memeriksa integritas buffer di salah satu sisi area yang dialokasikan dan mengeluarkan laporan kesalahan jika penimpaan telah terjadi.

Untuk menggunakan tumpukan debug

  • Tautkan build debug aplikasi Anda dengan versi debug pustaka runtime C.

Untuk mengubah satu atau beberapa _crtDbgFlag bidang bit dan membuat status baru untuk bendera

  1. Panggil _CrtSetDbgFlag dengan parameter newFlag yang diatur ke _CRTDBG_REPORT_FLAG (untuk mendapatkan status saat ini _crtDbgFlag) dan simpan nilai yang dikembalikan dalam variabel sementara.

  2. Aktifkan bit apa pun dengan menggunakan operator bitwise | ("atau") pada variabel sementara dengan bitmask yang sesuai (diwakili dalam kode aplikasi oleh konstanta manifes).

  3. Matikan bit lain dengan menggunakan operator bitwise & ("dan") pada variabel dengan operator bitwise ~ ("tidak" atau pelengkap) dari bitmask yang sesuai.

  4. Panggil _CrtSetDbgFlag dengan parameter newFlag yang diatur ke nilai yang disimpan dalam variabel sementara untuk membuat status baru untuk _crtDbgFlag.

    Misalnya, baris kode berikut mengaktifkan deteksi kebocoran otomatis dan menonaktifkan pemeriksaan untuk blok jenis _CRT_BLOCK:

    // Get current flag
    int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
    
    // Turn on leak-checking bit.
    tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
    
    // Turn off CRT block checking bit.
    tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;
    
    // Set flag to the new value.
    _CrtSetDbgFlag( tmpFlag );
    

new, delete, dan _CLIENT_BLOCK alokasi dalam tumpukan debug C++

Versi debug pustaka run-time C berisi versi debug operator C++ new dan delete. Jika Anda menggunakan jenis alokasi _CLIENT_BLOCK, Anda harus memanggil versi debug operator new secara langsung atau membuat makro yang menggantikan operator new dalam mode debug, seperti yang ditunjukkan dalam contoh berikut:

/* MyDbgNew.h
 Defines global operator new to allocate from
 client blocks
*/

#ifdef _DEBUG
   #define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
   #define DEBUG_CLIENTBLOCK
#endif // _DEBUG

/* MyApp.cpp
        Use a default workspace for a Console Application to
 *      build a Debug version of this code
*/

#include "crtdbg.h"
#include "mydbgnew.h"

#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif

int main( )   {
    char *p1;
    p1 =  new char[40];
    _CrtMemDumpAllObjectsSince( NULL );
}

Versi Debug operator delete berfungsi dengan semua jenis blok dan tidak memerlukan perubahan dalam program Anda saat Anda mengompilasi versi Rilis.

Fungsi pelaporan status timbunan

Untuk mengambil rekam jepret ringkasan status timbunan pada waktu tertentu, gunakan struktur yang _CrtMemState ditentukan dalam <crtdbg.h>:

typedef struct _CrtMemState
{
    // Pointer to the most recently allocated block:
    struct _CrtMemBlockHeader * pBlockHeader;
    // A counter for each of the 5 types of block:
    size_t lCounts[_MAX_BLOCKS];
    // Total bytes allocated in each block type:
    size_t lSizes[_MAX_BLOCKS];
    // The most bytes allocated at a time up to now:
    size_t lHighWaterCount;
    // The total bytes allocated at present:
    size_t lTotalCount;
} _CrtMemState;

Struktur ini menyimpan pointer ke blok pertama (yang baru-baru ini dialokasikan) dalam daftar tertaut heap debug. Kemudian, dalam dua array, ia mencatat berapa banyak dari setiap jenis blok memori (_NORMAL_BLOCK, _CLIENT_BLOCK, , _FREE_BLOCKdan sebagainya) dalam daftar dan jumlah byte yang dialokasikan di setiap jenis blok. Akhirnya, ia mencatat jumlah byte tertinggi yang dialokasikan dalam tumpukan secara keseluruhan hingga titik itu, dan jumlah byte yang saat ini dialokasikan.

Fungsi pelaporan CRT lainnya

Fungsi berikut melaporkan status dan konten timbunan, dan menggunakan informasi untuk membantu mendeteksi kebocoran memori dan masalah lainnya.

Function Deskripsi
_CrtMemCheckpoint Menyimpan rekam jepret timbunan dalam struktur yang _CrtMemState disediakan oleh aplikasi.
_CrtMemDifference Membandingkan dua struktur status memori, menyimpan perbedaan di antara keduanya dalam struktur status ketiga, dan mengembalikan TRUE jika kedua status berbeda.
_CrtMemDumpStatistics Mencadangkan struktur tertentu _CrtMemState . Struktur mungkin berisi rekam jepret status tumpukan debug pada saat tertentu atau perbedaan antara dua rekam jepret.
_CrtMemDumpAllObjectsSince Membuang informasi tentang semua objek yang dialokasikan sejak rekam jepret tertentu diambil dari timbunan atau sejak awal eksekusi. Setiap kali mencadangkan _CLIENT_BLOCK blok, ia memanggil fungsi kait yang disediakan oleh aplikasi, jika telah diinstal menggunakan _CrtSetDumpClient.
_CrtDumpMemoryLeaks Menentukan apakah ada kebocoran memori yang terjadi sejak awal eksekusi program dan, jika demikian, membuang semua objek yang dialokasikan. Setiap kali _CrtDumpMemoryLeaks mencadangkan _CLIENT_BLOCK blok, ia memanggil fungsi kait yang disediakan oleh aplikasi, jika telah diinstal menggunakan _CrtSetDumpClient.

Melacak permintaan alokasi timbunan

Mengetahui nama file sumber dan nomor baris makro pernyataan atau pelaporan sering berguna dalam menemukan penyebab masalah. Hal yang sama tidak mungkin berlaku untuk fungsi alokasi timbunan. Meskipun Anda dapat menyisipkan makro di banyak titik yang sesuai di pohon logika aplikasi, alokasi sering dikubur dalam fungsi yang disebut dari banyak tempat berbeda pada waktu yang berbeda. Pertanyaannya bukan baris kode apa yang membuat alokasi yang buruk. Sebaliknya, salah satu dari ribuan alokasi yang dibuat oleh baris kode itu buruk, dan mengapa.

Nomor permintaan alokasi unik dan _crtBreakAlloc

Ada cara sederhana untuk mengidentifikasi panggilan alokasi timbunan tertentu yang menjadi buruk. Ini memanfaatkan nomor permintaan alokasi unik yang terkait dengan setiap blok dalam tumpukan debug. Ketika informasi tentang blok dilaporkan oleh salah satu fungsi cadangan, nomor permintaan alokasi ini diapit kurung kurawal (misalnya, "{36}").

Setelah Anda mengetahui nomor permintaan alokasi dari blok yang dialokasikan secara tidak benar, Anda dapat meneruskan nomor ini untuk _CrtSetBreakAlloc membuat titik henti. Eksekusi akan rusak tepat sebelum mengalokasikan blok, dan Anda dapat melakukan backtrack untuk menentukan rutinitas apa yang bertanggung jawab atas panggilan buruk. Untuk menghindari kompilasi ulang, Anda dapat menyelesaikan hal yang sama di debugger dengan mengatur _crtBreakAlloc ke nomor permintaan alokasi yang Anda minati.

Membuat versi debug dari rutinitas alokasi Anda

Pendekatan yang lebih kompleks adalah membuat versi Debug dari rutinitas alokasi Anda sendiri, sebanding dengan _dbg versi fungsi alokasi timbunan. Anda kemudian dapat meneruskan argumen file sumber dan nomor baris ke rutinitas alokasi timbunan yang mendasar, dan Anda akan segera dapat melihat dari mana alokasi buruk berasal.

Misalnya, aplikasi Anda berisi rutinitas yang umum digunakan yang mirip dengan contoh berikut:

int addNewRecord(struct RecStruct * prevRecord,
                 int recType, int recAccess)
{
    // ...code omitted through actual allocation...
    if ((newRec = malloc(recSize)) == NULL)
    // ... rest of routine omitted too ...
}

Dalam file header, Anda dapat menambahkan kode seperti contoh berikut:

#ifdef _DEBUG
#define  addNewRecord(p, t, a) \
            addNewRecord(p, t, a, __FILE__, __LINE__)
#endif

Selanjutnya, Anda dapat mengubah alokasi dalam rutinitas pembuatan catatan Anda sebagai berikut:

int addNewRecord(struct RecStruct *prevRecord,
                int recType, int recAccess
#ifdef _DEBUG
               , const char *srcFile, int srcLine
#endif
    )
{
    /* ... code omitted through actual allocation ... */
    if ((newRec = _malloc_dbg(recSize, _NORMAL_BLOCK,
            srcFile, scrLine)) == NULL)
    /* ... rest of routine omitted too ... */
}

Sekarang nama file sumber dan nomor baris tempat addNewRecord dipanggil akan disimpan di setiap blok yang dihasilkan yang dialokasikan dalam tumpukan debug dan akan dilaporkan ketika blok tersebut diperiksa.

Baca juga

Penelusuran kesalahan Kode Asli