Поделиться через


Куча отладки CRT

В этом разделе представлен полный обзор отладочной кучи CRT.

Содержание раздела

Управление памятью и отладочная куча

Типы блоков в отладочной куче

Функции отладочной кучи

Использование отладочной кучи

новый, удаление и _CLIENT_BLOCKs в C, C-++ отладочная куча

Функции создания отчетов о состоянии кучи

Отслеживание запросов выделения кучи

Управление памятью и отладочная куча

Две самые распространенные и трудноразрешимые проблемы, с которыми сталкиваются программисты, — это перезапись конца выделенного буфера и утечки памяти (невозможность освободить выделенную память, когда она уже не нужна).Отладочная куча предоставляет мощные средства для решения подобных проблем с памятью.

Отладочные версии функций кучи вызывают стандартные или базовые версии, используемые в окончательных построениях.Когда запрашивается блок памяти, диспетчер отладочной кучи выделяет из основной кучи блок памяти немного больше требуемого, и возвращает указатель на часть этого блока.Например, допустим, приложение содержит вызов: malloc( 10 ).В окончательном построении malloc вызовет базовую программу выделения памяти в куче, которая запросит выделение 10 байтов.А в отладочном построении malloc вызовет _malloc_dbg, который затем уже сам вызовет базовую программу выделения памяти в куче и запросит выделение 10 байтов и около 36 байтов дополнительно.Все результирующие блоки памяти в отладочной куче подключаются к единому связанному списку, который упорядочивает их по времени выделения.

Дополнительная память, выделяемая программами выделения памяти в куче, используется для учета используемых системных ресурсов, для указателей, связывающих отладочные блоки памяти вместе, и для небольших буферов с обеих сторон данных для перехватывания перезаписи выделенной области.

На текущий момент структура заголовка блока, используемая для хранения данных учета использованных системных ресурсов отладочной кучи, объявляется в файле заголовка DBGINT.H.

typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
    struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
    struct _CrtMemBlockHeader *pBlockHeaderPrev;
    char *szFileName;    // File name
    int nLine;           // Line number
    size_t nDataSize;    // Size of user block
    int nBlockUse;       // Type of block
    long lRequest;       // Allocation number
// Buffer just before (lower than) the user's memory:
    unsigned char gap[nNoMansLandSize];
} _CrtMemBlockHeader;

/* In an actual memory block in the debug heap,
 * this structure is followed by:
 *   unsigned char data[nDataSize];
 *   unsigned char anotherGap[nNoMansLandSize];
 */

Буферы NoMansLand по обеим сторонам участка области блока пользовательских данных в настоящее время - 4 байта, и заполняются с известным байтовое значение, используемое процедурами отладочной кучи, чтобы убедиться, что не были перезаписаны ограничения блоков памяти пользователя.Новые блоки памяти отладочная куча тоже заполняет фиксированными значениями.Если хранить освобожденные блоки в связанном списке отладочной кучи (как объясняется ниже), эти блоки также будут заполняться фиксированным значением.В данный момент используются следующие действительные значения байтов:

  • NoMansLand (0xFD)
    Буферы "NoMansLand" по обеим сторонам участка памяти, используемой приложением, заполняются значением 0xFD.

  • Освобожденные блоки (0xDD)
    Освобожденные блоки в связанном списке отладочной кучи хранятся как неиспользуемые блоки и, если флаг _CRTDBG_DELAY_FREE_MEM_DF установлен, они заполняются значением 0xDD.

  • Новые объекты (0xCD)
    Новые объекты при выделении памяти заполняются значением 0xCD.

    Содержание раздела

Типы блоков в отладочной куче

Каждому блоку памяти в отладочной куче присвоен один из пяти типов выделений памяти.Эти типы отслеживаются и по-разному фиксируются в отчетах в зависимости от целей: обнаружение утечки памяти или отчет о состоянии.Тип блока можно задать, выделив его с помощью непосредственного вызова одной из функций выделения отладочной кучи, например _malloc_dbg.Пять типов блоков памяти в отладочной куче (задаются в nBlockUse — члене структуры _CrtMemBlockHeader) следующие:

  • _NORMAL_BLOCK
    Вызов malloc или calloc создает обычный блок.Если планируется использование только обычных блоков и не требуются клиентские, можно определить _CRTDBG_MAP_ALLOC, которая преобразует все вызовы выделений кучи в их отладочные эквиваленты в отладочном построении.Такой подход позволяет хранить сведения об имени файла и номере строки для каждого выделения в заголовке соответствующего блока.

  • _CRT_BLOCK
    Блоки памяти, выделенные для внутреннего использования несколькими функциями библиотеки CRT, помечаются как CRT-блоки и могут обрабатываться отдельно.В результате для обнаружения утечки и других операций эти блоки несущественны.Выделение памяти никогда не работает с блоками типа CRT (не выделяет, не перераспределяет и не освобождает).

  • _CLIENT_BLOCK
    В целях отладки приложение может специально отслеживать данную группу выделений путем выделения их как блоков памяти этого типа, применяя явные вызовы функций отладочной кучи.MFC, например, выделяет все CObjects как клиентские блоки; другие приложения могут в клиентских блоках хранить другие объекты памяти.Можно также задавать подтипы клиентских блоков — для более глубокого контроля.Чтобы задать подтип клиентского блока, сдвиньте номер влево на 16 бит и примените для него операцию OR с _CLIENT_BLOCK.Например:

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

    Клиентская функция-ловушка для дампа объекта, хранящегося в клиентском блоке, может быть установлена с помощью _CrtSetDumpClient и потом вызываться всякий раз, когда отладочной функцией создается дамп клиентского блока.Для вызова этой же функции, предоставляемой приложением для каждого клиентского блока в отладочной куче также может применяться _CrtDoForAllClientObjects.

  • _FREE_BLOCK
    Обычно это блоки, которые освобождаются и удаляются из списка.Чтобы удостовериться, что в освобожденную память не продолжается запись, или чтобы эмулировать условия нехватки памяти, можно выбрать хранение освобожденных блоков в связанном списке, помеченных как свободные и заполненных фиксированным значением байта (на данный момент 0xDD).

  • _IGNORE_BLOCK
    Операции отладочной кучи можно на время отключить.В течение этого времени блоки памяти хранятся в списке, но помечаются как пропускаемые блоки.

Чтобы определить тип и подтип данного блока, используются функция _CrtReportBlockType и макросы _BLOCK_TYPE и _BLOCK_SUBTYPE.Макросы определяются (в crtdbg.h) следующим образом:

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

Содержание раздела

Функции отладочной кучи

Из кода могут быть доступны многие функции отладочной кучи.В следующих разделах описываются эти возможности и способы их использования.

  • _CrtCheckMemory
    Например, функцию _CrtCheckMemory можно применить для проверки целостности кучи в любой точке.Эта функция проверяет каждый блок памяти в куче, оценивает допустимость данных его заголовка, а также удостоверяется, что буферы не были изменены.

  • _CrtSetDbgFlag
    Можно контролировать, как отладочная куча отслеживает выделения, с помощью внутреннего флага _crtDbgFlag, который можно читать и устанавливать функцией _CrtSetDbgFlag.Изменяя этот флаг, можно указать отладочной куче по завершении программы проверять память на утечки и формировать отчет при их обнаружении.Аналогично можно задать, чтобы освобожденные блоки не удалялись из связанного списка — для эмуляции условий нехватки памяти.Когда куча проверена, эти освобожденные блоки проверяются на целостность, чтобы убедиться, что они не были повреждены.

    Флаг**_crtDbgFlag** содержит следующие битовые поля:

    Битовое поле

    Default

    value

    Описание

    _CRTDBG_ALLOC_MEM_DF

    On

    Включает отладочное выделение.Если этот бит отключен, выделения остаются скрепленными вместе, но типы их блоков — _IGNORE_BLOCK.

    _CRTDBG_DELAY_FREE_MEM_DF

    Off

    Препятствует действительному освобождению памяти, как при эмуляции условий нехватки памяти.Если этот бит включен, освобожденные блоки хранятся в списке отладочной кучи, но помечаются как _FREE_BLOCK и заполняются фиксированным значением байтов.

    _CRTDBG_CHECK_ALWAYS_DF

    Off

    Причины _CrtCheckMemory, вызываемый в каждом выделении и освобождение.Это замедляет выполнение, но позволяет быстро перехватывать ошибки.

    _CRTDBG_CHECK_CRT_DF

    Off

    Блоки, помеченных как _CRT_BLOCK, в операции утечк- обнаружения и сравнения состояний.Если этот бит выключен, память, используемая внутренне библиотекой CRT, во время таких операций не обрабатывается.

    _CRTDBG_LEAK_CHECK_DF

    Off

    Принудительно запускает диагностирование утечек по выходе из программы — через вызов _CrtDumpMemoryLeaks.Если при попытке приложения освободить всю выделенную память происходит сбой, создается отчет об ошибке.

Содержание раздела

Использование отладочной кучи

Все функции кучи, такие как malloc, free, calloc, realloc, new и delete, разрешают своим отладочным эквивалентам действовать в отладочной куче.Когда освобождается блок памяти, отладочная куча автоматически проверяет целостность буферов по обеим сторонам выделенной области и выдает отчет об ошибке в случае их перезаписи.

Использование отладочной кучи

  • Свяжите отладочное построение приложения с отладочной версией библиотеки CRT.

Изменение одного или более битовых полей флага _crtDbgFlag и создание нового состояния флага

  1. Вызовите _CrtSetDbgFlag с параметром newFlag, равным _CRTDBG_REPORT_FLAG (чтобы получить текущее состояние _crtDbgFlag), и сохраните возвращенное значение во временной переменной.

  2. Включите любые биты, применив операцию побитового OR для этой временной переменной и соответствующей битовой маски (представленной в коде приложения константой манифеста).

  3. Отключите остальные биты, применив побитовую операцию AND (символ амперсанда "&") для переменной и NOT (символ тильды "~") соответствующей битовой маски.

  4. Вызовите _CrtSetDbgFlag с параметром newFlag со значением, сохраненным в этой временной переменной, чтобы создать новое состояние для _crtDbgFlag.

Например, следующие строки кода включают автоматическое обнаружение утечек памяти и отключают проверку блоков типа _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 );

новый, удаление и _CLIENT_BLOCKs в C, C-++ отладочная куча

Отладочные версии библиотеки времени выполнения C содержат отладочных версий C C-++ операторов new и delete.Если используется тип _CLIENT_BLOCK, необходимо вызвать отладочная версия оператора new напрямую или создание макросов, которые заменяют оператор new в режиме отладки, как показано в следующем примере:

/* 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 );
}

Отладочная версия оператора delete работает со всеми типами блоков и не требует изменений в программе при компиляции версии выпуска.

Функции создания отчетов о состоянии кучи

_CrtMemState

Чтобы сохранить снимок состояния кучи на текущий момент, используется структура _CrtMemState, определенная в 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;

Эта структура сохраняет указатель на первый (выделенный последним) блок в связанном списке отладочной кучи.Затем в двух массивах она записывает, сколько блоков памяти каждого типа (_NORMAL_BLOCK, _CLIENT_BLOCK, _FREE_BLOCK и т. д.) имеется в списке, а также количество байтов, выделенных в блоке каждого типа.И наконец она записывает наибольшее количество байтов, выделенных в куче до настоящего времени, а также количество байтов, выделенных в данный момент.

Другие функции отчетов CRT

Эти функции отчитываются о состоянии и содержимом кучи, использование этих сведений помогает обнаружить утечки памяти и решить другие подобные проблемы.

Функция

Описание

_CrtMemCheckpoint

Сохраняет снимок кучи в структуре _CrtMemState для приложения.

_CrtMemDifference

Сравнивает две структуры состояния памяти, сохраняет в третьей структуре различие между ними и возвращает значение TRUE при нахождении различий.

_CrtMemDumpStatistics

Формирует дамп структуры _CrtMemState.Структура может содержать снимок состояния отладочной кучи на данный момент или различие между двумя состояниями.

_CrtMemDumpAllObjectsSince

Выводит сведения обо всех объектах, выделенных с момента получения данного снимка кучи или с начала выполнения.Каждый раз, когда он сбрасывает блок _CLIENT_BLOCK, он вызывает функцию-обработчик является приложением, если установить с помощью _CrtSetDumpClient.

_CrtDumpMemoryLeaks

Определяет, не произошла ли утечка памяти с начала выполнения программы, и, если произошла, выводит все выделенные объекты.Каждый раз, когда _CrtDumpMemoryLeaks сбрасывает блок _CLIENT_BLOCK, он вызывает функцию-обработчик является приложением, если установить с помощью _CrtSetDumpClient.

Содержание раздела

Отслеживание запросов выделения кучи

Хотя точное указание имени исходного файла и номера строки, на которой стоит утверждение или выполняется макрос, часто очень полезно для выявления причины проблемы, этого не всегда достаточно для функций выделения кучи.Поскольку макросы могут вставляться в разные места логического дерева приложения, выделение часто скрыто в специальной программе из библиотеки DLL, которая тоже может вызываться из разных мест и в разное время.И поэтому вопрос обычно не в том, какая строка кода осуществила некорректное выделение, а в том, которое из тысяч выделений, сделанных этой строкой, было некорректным и почему.

Уникальные номера запросов выделения и _crtBreakAlloc

Наиболее простой способ идентифицировать запрос на указанное выделение кучи, ставшее некорректным, — воспользоваться преимуществом уникального номера запроса выделения, связанного с каждым блоком в отладочной куче.Когда данные о блоке помещены в отчет одной из функций дампа, этот номер запроса выделения заключается в фигурные скобки (например, "{36}").

Узнав номер запроса выделения некорректно выделенного блока, можно его передать в _CrtSetBreakAlloc и создать точку останова.Перед выделением блока выполнение прервется, и можно вернуться и определить, какая процедура отвечает за плохой вызов.Во избежание перекомпиляции эти же действия можно проделать в отладчике, присвоив _crtBreakAlloc номер запроса интересующего выделения.

Создание отладочных версий процедур выделения

Несколько более сложный подход — создание отладочных версий собственных процедур выделения, сопоставимых с версиями _dbgфункций выделения кучи.Можно передать аргументы исходного файла и номера строки базовым процедурам выделения кучи и сразу же увидеть, где возникло проблемное выделение.

Например, допустим, что приложение содержит обычную процедуру, наподобие следующей:

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

В файле заголовка можно добавить следующий код:

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

Затем можно изменить выделение в процедуре создания записи следующим образом:

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

Теперь имя исходного файла и номер строки, где вызывалась addNewRecord, будут сохранены в каждом выделенном в отладочной куче блоке и помещены в отчет при проверке этого блока.

Содержание раздела

См. также

Другие ресурсы

Отладка машинного кода

Методы отладки CRT