Сведения о куче отладки 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
содержит следующие битовые поля:
Битовое поле | Значение по умолчанию | 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
битового поля и создание нового состояния для флага
Вызовите
_CrtSetDbgFlag
с параметромnewFlag
, равным_CRTDBG_REPORT_FLAG
(чтобы получить текущее состояние_crtDbgFlag
), и сохраните возвращенное значение во временной переменной.Включите все биты с помощью побитового
|
оператора ("или") во временной переменной с соответствующими битовыми масками (представленными в коде приложения константами манифеста).Отключите другие биты с помощью побитового
&
оператора ("и") в переменной с побитовой~
оператором ("не" или дополнением) соответствующих битовых масок.Вызовите
_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
, будут сохранены в каждом выделенном в отладочной куче блоке и помещены в отчет при проверке этого блока.