Использование MemDumperValidator
Расширение MemDumperValidator из DCRT-библиотеки значительно облегчает отладку памяти. По умолчанию DCRT-библиотека выдает сообщения об утечках памяти и проверках корректности тех блоков памяти, в которых оба типа записей (с начала — underwrites или с конца блока — overwrites) не подвергались разрушению. Оба отчета могут быть очень полезны, но если отчет об утечках памяти выглядит так, как показано ниже, то довольно трудно точно определить, в памяти какого типа произошла утечка:
Detected memory leaks
Dumping objects ->
с:\vc\INCLUDE\crtdbg.h(552) : (596} normal block at Ox008CD5BO,
24 bytes long.
Data: < k w k > 90 6B 8C 00 BO DD 8C 00 00 00 80 77 90 6В 8С 00
Object dump complete.
Как говорилось ранее, средств проверки корректности памяти, заданных по умолчанию, может оказаться недостаточно. Для отлавливания некоторых специальных видов записей в память необходима более глубокая проверка корректности (иначе вы вообще не сможете перехватывать такие записи). Дополнительные проверки корректности и информация отчетов об утечках памяти, обеспечиваемые расширением MemDumperValidator, особенно полезны во время отладки. Чем больше информации получено во время отладки, тем меньше времени она занимает.
Расширение MemDumperValidator использует в своей работе идентификаторы блоков памяти DCRT-библиотеки, что позволяет ему ассоциировать тип блока со специфическим набором функций работы с памятью, которым что-то известно о содержимом соответствующего блока. Каждому блоку памяти, распределенному через библиотеку DCRT, назначается специальный идентификатор, как показано в табл. 15.3. Типы блоков являются параметрами следующих функций распределения памяти библиотеки DCRT: _nh_maiioc_ dbg (new), _malloc_dbg (malloc), _calloc_dbg (calloc) И _realloc_dbg (realloc).
Таблица 15.3. Идентификаторы блоков памяти
Идентификатор блока | Описание |
_NORMAL_BLOCK |
Вызов обычной функции распределения (new, malloc или calloc) создает нормальные блоки. Определение #define _CRTDBG_MAP_ALLOC приводит к тому, что все распределения кучи по умолчанию являются нормальными блоками и связывают с блоком памяти имя исходного файла и номер строки, содержащей вызов соответствующей функции распределения |
_CRT_BLOCK |
Блоки памяти, распределяемые многими функциями библиотеки времени выполнения, помечаются как CRT-блоки, поэтому они могут быть обработаны отдельно (иначе, чем блоки других типов). В частности, появляется возможность исключить такие блоки из процедур обнаружения утечек памяти и других операций проверок корректности. Ваше приложение никогда не должно распределять, перераспределять или освобождать блоки подобного типа |
CLIENT BLOCK |
Если нужно, чтобы приложение выполняло специальную трассировку блоков распределенной памяти, то можно вызывать особые отладочные функции распределения, передавая им в качестве параметра специальное значение CLIENT BLOCK VALUE (см. ниже вызов heap alloc dbg после директивы #define). Можно прослеживать подтипы клиентских блоков, помещая 1 6-разрядное значение в 16 верхних разрядов значения блока, как показано ниже: #define CLIENT_BLOCK_VALUE(x) \ (_CLIENT_BLOCK | (x«16) ) heap alloc dbg ( 10, CLIENT BLOCK VALUE ( OxA) , _ FILE _ , _ LINE _ ) ; Для дампов блоков памяти этого типа (т. е. памяти, зарегистрированной в форме клиентских блоков) приложение может обеспечить функцию-обработчик (через функцию CrtSetDumpClient). Функция-обработчик будет вызываться всякий раз, когда функции DCRT-библиотеки потребуется выполнить дамп клиент- |
(прод.) |
ского блока. Кроме того, имеется специальная функция (__CrtDoForAlldientObjects), которая позволяет получить список всех клиентских блоков, распределенных к моменту ее вызова. MFC использует данный идентификатор для всех классов производных от класса cobject. Расширение MemDumperValidator тоже использует описанный механизм |
FREE BLOCK |
Вызов подпрограммы освобождения памяти обычно удаляет память из списков отладочной кучи. Однако если при обращении к функции CrtSetDbgFlag вы устанавливаете флажок CRTDBG DELAY FREE MEM DF, то память не освобождается, а выравнивается влево и заполняется символами OxDD |
IGNORE BLOCK |
Если вы временно отключаете трассировку DCRT-библиотеки, то любые распределения, выполненные после этого, будут помечаться как блоки Ignore |
Описать MemDumperValidator не так уж сложно, но вот заставить его работать — немного сложнее. В листинге 15.1 показан заголовочный файл MEMDUMPERVALIDATOR.H, который выполняет основную часть работ по инициализации. Включая в программу файла BUGSLAYERUTIL.H, вы автоматически включаете и MEMDUMPERVALIDATOR.H.
Листинг 15-1, JWEMDUMPERVAL1DATOR.H
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - -
"Debugging Applications" (Microsoft Press)
Copyright (с) 1997-2000 John Robbins — All rights reserved.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifndef _MEMDUMPERVALIDATOR_H
#define _MEMDUMPERVALIDATOR_H
// He включайте этот файл напрямую; вместо него включайте BUGSLAYER.H
#ifndef _BUGSLAYERUTIL_H
#error "Include BUGSLAYERUTIL.H instead of this file directly!"
#endif // _BUGSLAYERUTIL_H
// Включить заголовочный файл CRTDBG.H.
#include "MSJDBG.h"
#ifdef _cplusplus
extern "C" {
#endif // _ _cplusplus
// Эту библиотеку можно использовать только в отладочных построениях.
#ifdef _DEBUG
/////////////////////////////////////////////////////////////
// Директивы typedef для функций вьдачи дампов и проверки корректности
////////////////////////////////////////////////////////////////
// Функция выдачи дампов памяти. Единственный параметр этой функции -
// указатель на блок памяти. Эта функция выводит данные блока памяти
// одним из нескольких доступных ей способов, но, для того чтобы быть
// состоятельной, она использует механизм формирования отчетов,
// которым пользуется остальная часть DCRT-библиотеки.
typedef void (*PFNMEMDUMPER)(const void *);
// Функция проверки корректности (validating function).
//Ее первый параметр — блок памяти,
// корректность которого нужно проверить, а второй — контекстная
// информация, пересылаемая в функцию ValidateAllBlocks function.
typedef void (*PFNMEMVALIDATOR)(const void *, const void *);
////////////////////////////////////////////////////////////////
// Полезный макрос
////////////////////////////////////////////////////////////////
// Макрос, используемый для установки значения подтипа Client-блока.
// Использование этого макроса — единственное санкционированное средство
// установки значения поля dwValue в структуре DVINFO (см. ниже).
tdefine CLIENT_BLOCK_VALUE(x) (_CLIENT_BLOCK|(x«16))
// Макрос для выбора подтипа
Idefine CLIENT_BLOCK_SUBTYPE(х) ((х » 16) & 0xFFFF)
/////////////////////////////////////////////////////////////
// Заголовок, используемый для инициализации функций дампа и проверки
// корректности Client-блока специфического подтипа
////////////////////////////////////////////////////////////
typedef struct tag_DVINFO
{
// Значение подтипа Client-блоков. Это значение должно быть
// установлено с помощью определенного выше макроса.
// CLIENT_BLOCK_VALUE. Чтобы выяснить, как расширение назначает
// это число, см. функцию AddClientDV.
unsigned long dwValue ;
// Указатель на функцию дампа
PFNMEMDUMPER pfnDump
// Указатель на функцию проверки корректности
PFNMEMVALIDATOR pfnValidate;
} DVINFO, * LPDVINFO;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ФУНКЦИЯ : AddClientDV
ОБСУЖДЕНИЕ :
Добавляет в список функции дампа и проверки корректности
Client-блока. Если поле dwValue в структуре DVINFO равно О,
то назначается следующее значение из списка. Возвращаемое значение
должно всегда пересылаться в функцию _malloc_dbg в качестве
значения Client-блока.
Если значение подтипа устанавливается с помощью макроса
CLIENT_BLOCK__VALUE, то его можно использовать в качестве значения,
передаваемого в функцию _malloc_dbg.
Заметим, что соответствующей функции удаления не существует.
Почему возникает риск введения ошибок в отладочный код? Проблема
производительности отходит на задний план, когда речь заходит
о поиске ошибок.
ПАРАМЕТРЫ :
IpDVInfo — Указатель на структуру DVINFO
ВОЗВРАЩАЕТ :
1 — функции дампа и проверки корректности клиентского блока были
успешно добавлены.
0 — функции дампа и проверки корректности клиентского блока не могут
быть добавлены.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int BUGSUTIL_DLLINTERFACE _stdcall AddClientDV (LPDVINFO IpDVInfo);
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ФУНКЦИЯ : ValidateAllBlocks
ОБСУЖДЕНИЕ :
Проверяет все распределения памяти за пределами локальной кучи.
Кроме
того, просматривает все Client-блоки и вызывает специальные функции
проверки корректности для различных подтипов этих блоков.
Вероятно, лучше всего вызывать эту функцию с макросом VALIDATEALLBLOCKS, который показан ниже.
ПАРАМЕТРЫ :
pContext — Контекстная информация, которая будет передаваться
в каждую функцию проверки корректности.
ВОЗВРАЩАЕТ :
Ничего.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void BUGSOTIL_DLLINTERFACE _stdcall
ValidateAllBlocks ( void * pContext);
#ifdef _cplusplus
//////////////////////////////////////////////////////////////
// Макросы вспомогательных классов C++
//////////////////////////////////////////////////////////////
// Объявите этот макрос в своем классе как обычный MFC-макрос,
#define DECLAREJYEMDEBUG(classname)
public :
static DVINFO m_stDVInfo;
static void ClassDumper ( const void * pData);
static void ClassValidator ( const void * pData,
const void * pContext);
static void * operator new ( size_t nSize)
{
if ( 0 == m_stDVInfo.dwValue)
{
m_stDVTnfо.pfnDump = classname::ClassDumper;
m_stDVInfo.pfnValidate = classname::ClassValidator;
AddClientDV ( &m_stDVInfo);
}
return ( _malloc_dbg ( nSize
(int)m_stDVlnfо.dwValue,
_FILE_ ,
_LINE_ ) ) ;
}
static void * operator new ( size_t nSize ,
char * IpszFileName,
int nLine )
{
if ( 0 = m_stDVInfo.dwValue)
{
m_stDVInfo.pfnDump = classname::ClassDumper;
m_stDVInfo.pfnValidate = classname::ClassValidator;
AddClientDV ( &m_stDVInfo);
}
return ( _malloc_dbg ( nSize
(int)m_stDVInfо.dwValue,
IpszFileName ,
nLine )) ;
}
static void operator delete ( void * pData)
{
_free_dbg ( pData, (int)m_stDVInfo.dwValue);
}
// Объявите этот макрос в начале своего СРР-файла.
#define IMPLEMENT_MEMDEBUG(classname)
DVINFO classname::m_stDVInfо = { 0, 0, 0 }
// Макрос для отладочных распределений памяти.
Если определено
// символическое имя DEBUG_NEW, то этот макрос использовать нельзя.
#ifdef DEBUG_NEW
tdefine MEMDEBUG_NEW DEBUG_NEW
#else
#define MEMDEBUG_NEW new ( _FILE_, _LINE_)
#endif
#endif // идентификатор _cplusplus определен
//////////////////////////////////////////////////////////////////
// Вспомогательные С-макросы
/////////////////////////////////////////////////////////////////
// Используйте этот макрос для распределения памяти в С-стиле.
// Единственной проблемой при этом является необходимость работы со
// структурой DVINFO.
Idefine INITIALIZE_MEMDEBUG(IpDVInfo, pfnD, pfnV)
{
ASSERT ( FALSE == IsBadWritePtr ( IpDVInfo,
sizeof ( DVINFO)));
((LPDVINFO)IpDVInfo)->dwValue = 0;
((LPDVINFO)IpDVInfo)->pfnDump = pfnD;
((LPDVINFO)IpDVInfo)->pfnValidate = pfnV;
AddClientDV ( IpDVInfo);
}
// Макросы, которые преобразуют функции распределения памяти С-формата
//в более удобную для применения форму. Он избавляет вас от
// запоминания и кодирования различных значений блока DVINFO,
// используемых в функциях работы с памятью.
#define MEMDEBUG_MALLOC(IpDVInfo, nSize) \
_malloc_dbg ( nSize , \
((LPDVINFO)IpDVInfo)->dwValue, \
_FILE_ , \
_LINE_ )
#define MEMDEBUG_REALLOC(IpDVInfo, pBlock, nSize) \
__realloc_dbg ( pBlock , \
nSize , \
((LPDVINFO)IpDVInfo)->dwValue , \
_FILE_ , \
_LINE_ )
#define MEMDEBUG_EXPAND(IpDVInfo, pBlock, nSize) \
_expand_dbg( pBlock , \
nSize , \
' ((LPDVINFO)IpDVInfo)->dwValue , \
_FILE_ , \
_LINE_ )
#define MEMDEBUG_FREE(lpDVInfo, pBlock) \
_free_dbg ( pBlock , \
((LPDVINFO)IpDVInfo)->dwValue)
#define MEMDEBUG_MSIZE(IpDVInfo, pBlock} \
_msize_dbg ( pBlock, ((LPDVINFO)IpDVInfo)->dwValue)
// Макрос для вызова функции ValidateAllBlocks
#define VALIDATEALLBLOCKS(x) ValidateAllBlocks ( x)
#else // _DEBUG не определен.
#ifdef _cplusplus
#define DECLARE_MEMDEBUG(classname)
#define IMPLEMENT_MEMDEBUG(classname)
#define MEMDEBUG_NEW new
#endif // _cplusplus
#define INITIALIZE_MEMDEBUG(IpDVInfo, pfnD, pfnV)
#define MEMDEBUG_MALLOC(IpDVInfo, nSize) \
malloc ( nSize)
#define MEMDEBUG_REALLOC(IpDVInfo, pBlock, nSize) \
realloc ( pBlock, nSize)
#define MEMDEBUG_EXPAND(IpDVInfo, pBlock, nSize) \
_expand ( pBlock, nSize)
#define MEMDEBUG_FREE(IpDVInfo, pBlock) \
free ( pBlock) ttdefine MEMDEBUG_MSIZE(IpDVInfo, pBlock) \
_msize ( pBlock)
#define VALIDATEALLBLOCKS(x)
#endif // _DEBUG ttifdef _cplusplus
}
#endif // _cplusplus
#endif // _MEMDUMPERVALIDATOR_H