Куча отладки 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 и создание нового состояния флага
Вызовите _CrtSetDbgFlag с параметром newFlag, равным _CRTDBG_REPORT_FLAG (чтобы получить текущее состояние _crtDbgFlag), и сохраните возвращенное значение во временной переменной.
Включите любые биты, применив операцию побитового OR для этой временной переменной и соответствующей битовой маски (представленной в коде приложения константой манифеста).
Отключите остальные биты, применив побитовую операцию AND (символ амперсанда "&") для переменной и NOT (символ тильды "~") соответствующей битовой маски.
Вызовите _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
Эти функции отчитываются о состоянии и содержимом кучи, использование этих сведений помогает обнаружить утечки памяти и решить другие подобные проблемы.
Функция |
Описание |
---|---|
Сохраняет снимок кучи в структуре _CrtMemState для приложения. |
|
Сравнивает две структуры состояния памяти, сохраняет в третьей структуре различие между ними и возвращает значение TRUE при нахождении различий. |
|
Формирует дамп структуры _CrtMemState.Структура может содержать снимок состояния отладочной кучи на данный момент или различие между двумя состояниями. |
|
Выводит сведения обо всех объектах, выделенных с момента получения данного снимка кучи или с начала выполнения.Каждый раз, когда он сбрасывает блок _CLIENT_BLOCK, он вызывает функцию-обработчик является приложением, если установить с помощью _CrtSetDumpClient. |
|
Определяет, не произошла ли утечка памяти с начала выполнения программы, и, если произошла, выводит все выделенные объекты.Каждый раз, когда _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, будут сохранены в каждом выделенном в отладочной куче блоке и помещены в отчет при проверке этого блока.
Содержание раздела