Отслеживание запросов выделения кучи
Обновлен: Ноябрь 2007
Этот раздел применим к:
Выпуск |
Visual Basic |
C# |
C++ |
Web Developer |
---|---|---|---|---|
Экспресс-выпуск |
Только машинные коды |
|||
Standard |
Только машинные коды |
|||
Pro и Team |
Только машинные коды |
Обозначения:
Применяется |
|
Неприменимо |
|
Команда или команды скрытые по умолчанию. |
Хотя точное указание имени исходного файла и номера строки, на которой стоит утверждение или выполняется макрос, часто очень полезно для выявления причины проблемы, этого не всегда достаточно для функций выделения кучи. Поскольку макросы могут вставляться в разные места логического дерева приложения, выделение часто скрыто в специальной программе из библиотеки 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, будут сохранены в каждом выделенном в отладочной куче блоке и помещены в отчет при проверке этого блока.