Сведения о куче отладки CRT

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

Поиск переполнений буфера с помощью отладочной кучи

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

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

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

В настоящее время структура заголовка блока, используемая для хранения сведений о кладовке отладки, объявлена в <crtdbg.h> заголовке и определена в исходном <debug_heap.cpp> файле CRT. Концептуально это похоже на эту структуру:

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;

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

no_mans_land (0xFD)
Буферы "no_mans_land" на обеих сторонах памяти, используемой приложением, в настоящее время заполняются 0xFD.

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

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

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

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

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

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

_CLIENT_BLOCK
В целях отладки приложение может специально отслеживать данную группу выделений путем выделения их как блоков памяти этого типа, применяя явные вызовы функций отладочной кучи. Например, MFC выделяет все CObject объекты как блоки клиента. Другие приложения могут хранить различные объекты памяти в клиентских блоках. Можно также задавать подтипы клиентских блоков — для более глубокого контроля. Чтобы задать подтип клиентского блока, сдвиньте номер влево на 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 Description
_CRTDBG_ALLOC_MEM_DF Вкл Включает отладочное выделение. Если этот бит отключен, выделения остаются в цепочке, но их тип блока равен _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF Выключено Препятствует действительному освобождению памяти, как при эмуляции условий нехватки памяти. Если этот бит включен, освобожденные блоки хранятся в связанном списке отладочной кучи, но помечены как _FREE_BLOCK и заполнены специальным значением байтов.
_CRTDBG_CHECK_ALWAYS_DF Выключено _CrtCheckMemory Причины вызова при каждом выделении и размещении сделки. Выполнение выполняется медленнее, но оно быстро перехватывает ошибки.
_CRTDBG_CHECK_CRT_DF Выключено Приводит к тому, что блоки, помеченные как тип _CRT_BLOCK , будут включены в операции обнаружения утечки и различия состояния. Если этот бит выключен, память, используемая внутренне библиотекой CRT, во время таких операций не обрабатывается.
_CRTDBG_LEAK_CHECK_DF Выключено Вызывает утечку проверка выполнения при выходе программы через вызов_CrtDumpMemoryLeaks. Если при попытке приложения освободить всю выделенную память происходит сбой, создается отчет об ошибке.

Настройка отладочной кучи

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

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

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

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

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

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

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

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

new, deleteи _CLIENT_BLOCK выделения в куче отладки 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 в <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

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

Function Description
_CrtMemCheckpoint Сохраняет моментальный снимок кучи в структуре, предоставленной _CrtMemState приложением.
_CrtMemDifference Сравнивает две структуры состояния памяти, сохраняет в третьей структуре различие между ними и возвращает значение TRUE при нахождении различий.
_CrtMemDumpStatistics Дампает определенную _CrtMemState структуру. Структура может содержать снимок состояния отладочной кучи на данный момент или различие между двумя состояниями.
_CrtMemDumpAllObjectsSince Выводит сведения обо всех объектах, выделенных с момента получения данного снимка кучи или с начала выполнения. Каждый раз при дампах _CLIENT_BLOCK блока он вызывает функцию перехватчика, предоставляемую приложением, если он был установлен с помощью _CrtSetDumpClient.
_CrtDumpMemoryLeaks Определяет, не произошла ли утечка памяти с начала выполнения программы, и, если произошла, выводит все выделенные объекты. Каждый раз при _CrtDumpMemoryLeaks дампах _CLIENT_BLOCK блока он вызывает функцию перехватчика, предоставляемую приложением, если он был установлен с помощью _CrtSetDumpClient.

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

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

Уникальные номера запросов на выделение и _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, будут сохранены в каждом выделенном в отладочной куче блоке и помещены в отчет при проверке этого блока.

См. также

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