
Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdf

624 |
ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода |
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
\ |
|
return ( _malloc_dbg ( nSize |
|
, |
\ |
|
(int)m_stBSMDVINFO.dwValue , |
\ |
||
|
lpszFileName |
, |
\ |
|
|
nLine |
|
) ) ; |
\ |
|
} |
|
|
\ |
|
static void * operator new ( size_t nSize |
, |
\ |
|
|
int |
/*iBlockType*/ , |
\ |
|
|
char * lpszFileName |
, |
\ |
|
|
int |
nLine |
) |
\ |
|
{ |
|
|
\ |
|
if ( 0 == m_stBSMDVINFO.dwValue ) |
|
\ |
|
|
{ |
|
|
\ |
|
m_stBSMDVINFO.pfnDump |
= classname::ClassDumper ; |
\ |
|
|
m_stBSMDVINFO.pfnValidate = classname::ClassValidator ; \ |
|||
|
AddClientDV ( &m_stBSMDVINFO ) ; |
|
\ |
|
|
} |
|
|
\ |
|
return ( _malloc_dbg ( nSize |
|
, |
\ |
|
(int)m_stBSMDVINFO.dwValue , |
\ |
||
|
lpszFileName |
, |
\ |
|
|
nLine |
|
) ) ; |
\ |
|
} |
|
|
\ |
|
static void operator delete ( void * pData ) |
|
\ |
|
|
{ |
|
|
\ |
|
_free_dbg ( pData , (int)m_stBSMDVINFO.dwValue ) ; |
\ |
||
|
} |
|
|
\ |
|
static void __cdecl operator delete ( void * _P , |
\ |
||
|
|
int |
, |
\ |
|
|
char * |
, |
\ |
|
|
int |
) |
\ |
|
{ |
|
|
\ |
|
::operator delete ( _P ) ; |
|
|
\ |
|
} |
|
|
\ |
|
static void __cdecl operator delete[] ( void * _P , |
\ |
||
|
|
int |
, |
\ |
|
|
char * |
, |
\ |
|
|
int |
) |
\ |
|
{ |
|
|
\ |
|
::operator delete[]( _P ) ; |
|
|
\ |
|
} |
|
|
|
// Объявляйте этот макрос в начале своего файла .CPP. |
|
|||
#define IMPLEMENT_MEMDEBUG(classname) |
|
|
\ |
|
|
BSMDVINFO classname::m_stBSMDVINFO = { 0 , 0 , 0 } |
|
//Макрос для отладочного выделения памяти. Если определен
//макрос DEBUG_NEW, можно использовать его.
#ifdef DEBUG_NEW
#define MEMDEBUG_NEW DEBUG_NEW #else


626 |
ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода |
|
|
|
|
|
|
#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(lpBSMDVINFO , pfnD , pfnV )
#define MEMDEBUG_MALLOC(lpBSMDVINFO , nSize) \
malloc ( nSize )
#define MEMDEBUG_REALLOC(lpBSMDVINFO , pBlock , nSize) \
realloc ( pBlock , nSize )
#define MEMDEBUG_EXPAND(lpBSMDVINFO , pBlock , nSize) \
_expand ( pBlock , nSize )
#define MEMDEBUG_FREE(lpBSMDVINFO , pBlock) \
free ( pBlock )
#define MEMDEBUG_MSIZE(lpBSMDVINFO , pBlock) \
|
_msize ( pBlock ) |
#define VALIDATEALLBLOCKS(x) |
|
#endif |
// _DEBUG |
#ifdef __cplusplus |
|
} |
|
#endif |
// __cplusplus |
#endif |
// _MEMDUMPERVALIDATOR_H |
Использование MemDumperValidator в программах C++
К счастью, настроить класс C++ для его обработки MemDumperValidator’ом довольно просто. Для этого нужно добавить перед объявлением нужного класса директиву #pragma push_macro ("new") и отменить определение new. После объявления надо восстановить определение new, применив директиву #pragma pop_macro ("new"). В объявлении класса C++ просто укажите макрос DECLARE_MEMDEBUG с именем клас са в качестве параметра. Этот макрос чем то похож на «магические» макросы MFC: он тоже расширяется в пару объявлений данных и метода. Изучая листинг 17 2, вы заметите шесть встраиваемых функций для new и delete, обрабатывающих любой тип вызова этих операторов с синтаксисом размещения. Если какой то из этих операторов определен в вашем классе, извлеките код из расширенных операто ров и поместите его в операторах вашего класса.
В файле реализации класса C++ нужно указать макрос IMPLEMENT_MEMDEBUG с именем класса в качестве параметра. Этот макрос подготавливает статическую перемен ную для вашего класса. Макросы DECLARE_MEMDEBUG и IMPLEMENT_MEMDEBUG расширяются
ГЛАВА 17 Стандартная отладочная библиотека C и управление памятью |
627 |
|
|
только в отладочных компоновках, поэтому их не нужно включать в блок услов ной компиляции.
Указав оба макроса в корректном месте, вам нужно реализовать два метода, которые будут записывать дампа и проверять память вашего класса. Прототипы этих методов указаны чуть ниже. Очевидно, что их нужно заключать в блок ус ловной компиляции, чтобы они не компилировались в заключительных компо новках.
static void ClassDumper ( const void * pData ) ; static void ClassValidator ( const void * pData,
const void * pContext ) ;
Параметр pData, одинаковый для обоих методов, — это указатель на блок па мяти объекта. Для получения готового к использованию указателя вам нужно только привести pData к типу указателя на ваш класс. Что бы вы ни делали при записи дампа или проверке памяти, рассматривайте значение pData как супер только для чтения, или вы с легкостью внесете в свой код столько ошибок, сколько собира лись предотвратить. Второй параметр метода ClassValidator — pContext — это па раметр контекста, передаваемый вами в функцию ValidateAllBlocks. Подробнее о функции ValidateAllBlocks см. раздел «Глубокая проверка».
Что до реализации метода ClassDumper, то я могу дать лишь два совета. Во пер вых, старайтесь использовать макросы _RPTn и _RPTFn библиотеки DCRT, чтобы ваш форматированный вывод записывался в дамп там же, где и остальная информа ция библиотеки DCRT. Во вторых, завершайте свой вывод комбинацией «возврат каретки/перевод строки» (CR/LF), потому что макросы библиотеки DCRT не вы полняют форматирования.
Подключение функций записи дампа и проверки памяти для класса C++ кажется почти тривиальным. А структуры данных C, которые вам также хотелось бы запи сывать в понятные и удобные дампы? Увы, их обработка требует большей работы.
Использование MemDumperValidator в программах C
Вы недоумеваете, зачем беспокоиться о поддержке C? Все просто: на этом языке написана масса используемых мной и вами программ. Как хотите, но некоторые из этих приложений и модулей тоже работают с памятью.
Чтобы использовать MemDumperValidator в программе C, нужно сначала объя вить структуру BSMDVINFO для каждого типа памяти, который вы хотите проверять и записывать в дамп. Макросы C++ объявляют методы записи дампа и проверки памяти автоматически, однако в C кое что придется сделать самостоятельно. По мните: все макросы, про которые я здесь говорю, должны получать указатель на специфическую структуру BSMDVINFO.
Прототипы функций записи дампа и проверки памяти C аналогичны прото типам методов C++ — нет только ключевого слова static. Как и при объявлении уникальных структур BSMDVINFO для блоков памяти, реализацию всех функций за писи дампа и проверки C можно поместить в один файл.
Прежде чем вы начнете выделять, записывать и проверять память в програм мах C, вы должны сообщить расширению MemDumperValidator о подтипе клиен тского блока и функциях записи дампа и проверки памяти. Эта информация пе

628 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
редается расширению MemDumperValidator при помощи макроса INITIALIZE_MEM7 DEBUG, который в качестве параметров принимает указатель на специфическую структуру BSMDVINFO и указатели на функции записи дампа и проверки. Вам нужно будет выполнять этот макрос перед выделением любого блока памяти соответству ющего типа.
Наконец (и в этом смысле работать с памятью в C++ гораздо легче, чем в C), для выделения, освобождения, перераспределения, расширения или получения размера блока вы должны использовать набор макросов, передающих значение блока нужной функции работы с памятью. Так, если ваша структура BSMDVINFO на зывается stdvBlockInfo, нужно выделить блоки памяти в программе C:
MEMDEBUG_MALLOC ( &stdvBlockInfo , sizeof ( x ) ) ;
В конце листинга 17 2 содержатся все макросы для функций работы с памя тью языка C. Можно запомнить структуры BSMDVINFO для каждого типа выделения памяти, но это непрактично, поэтому для обработки структур BSMDVINFO лучше написать макросы оболочки — тогда вам нужно передавать в свои макросы обо лочки только обычные параметры функций работы с памятью.
Глубокая проверка
Польза записи дампов при помощи MemDumperValidator несомненна, тогда как назначение метода проверки не столь очевидно, пусть даже он позволяет выпол нять глубокую проверку блока памяти. Зачастую функция проверки может быть даже пустой, если класс содержит только несколько переменных строк. И все же функция проверки памяти все равно может оказаться бесценной, предоставляя великолепные отладочные возможности. Одна из причин того, что я начал исполь зовать глубокую проверку, заключалась в получении второго уровня проверки данных для набора разработанных мной базовых классов. Хотя функция провер ки не должна заменять вам старую добрую проверку параметров и вводимой ин формации, она может предоставить дополнительное подтверждение корректно сти данных. На основе глубокой проверки можно создать и вторую линию обо роны против записи информации по случайным адресам памяти.
Самый очевидный способ использования функции проверки — контроль слож ных структур данных после проведения над ними каких либо операций. Я, напрмер, как то попал в сложную ситуацию, когда из за ограничений памяти мне нужно было сделать так, чтобы две отдельных ссылающихся на себя структуры данных работали с одними и теми же объектами, находящимися в выделенной памяти. После заполнения структур большим набором данных я изучил при помощи фун кции проверки памяти отдельные блоки кучи и убедился в правильных значени ях ссылок. Конечно, я мог написать код просмотра каждой структуры данных, но я знал, что любой написанный мной код будет потенциальным источником оши бок. Функция проверки памяти позволила мне применить для изучения выделен ных блоков уже протестированный код и проверить структуры данных с разных позиций, так как блоки памяти располагались в порядке выделения, а не в отсор тированном порядке.
Хотя в C настройка выделения памяти сложнее, чем в C++, применение функ ции проверки памяти в обоих языках одинаково. Для этого нужно только вызвать


630ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
};
#pragma pop_macro ( "new" )
//Этот макрос создает статическую структуру BSMDVINFO. IMPLEMENT_MEMDEBUG ( TestClass ) ;
//Методы, которые вы должны реализовать
//для записи дампов и проверки памяти.
#ifdef _DEBUG |
|
|
void TestClass::ClassDumper ( const void * pData ) |
|
|
{ |
|
|
TestClass * pClass = (TestClass*)pData ; |
|
|
_RPT1 ( _CRT_WARN |
, |
|
" TestClass::ClassDumper : %S\n" |
, |
|
pClass7>m_szData |
) ; |
|
} |
|
|
void TestClass::ClassValidator ( const void * pData |
, |
|
const void * |
|
) |
{ |
|
|
// Выполняйте здесь проверку данных. |
|
|
TestClass * pClass = (TestClass*)pData ; |
|
|
_RPT1 ( _CRT_WARN |
, |
|
" TestClass::ClassValidator : %S\n" , |
|
|
pClass7>m_szData |
) ; |
|
} |
|
|
#endif |
|
|
typedef struct tag_SimpleStruct
{
TCHAR szName[ 256 ] ;
TCHAR szRank[ 256 ] ;
} SimpleStruct ;
// Функции записи дампа и проверки памяти для простых строк. void DumperOne ( const void * pData )
{
_RPT1 ( _CRT_WARN , " Data is : %S\n" , pData ) ;
}
void ValidatorOne ( const void * pData , const void * pContext )
{
// Выполняйте здесь |
проверку данных строки. |
|
_RPT2 ( _CRT_WARN |
|
, |
" Validator |
called with : %s : 0x%08X\n" , |
|
pData |
|
, |
pContext |
|
) ; |
}
// Функции записи дампа и проверки памяти для структуры. void DumperTwo ( const void * pData )


632 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
// Проверка всех блоков списка. VALIDATEALLBLOCKS ( NULL ) ;
_tprintf ( _T ( "At end of main\n" ) ) ;
// Дамп каждого блока создается при проверке утечек памяти.
}
Реализация MemDumperValidator
Реализация функций MemDumperValidator оказалась довольно простой. Первая неожиданная проблема, с которой я должен был справиться, была в том, что в библиотеке DCRT не был документирован способ получения значений блоков памяти функциями ловушками. Функциям ловушкам передается только указатель на данные пользователя, а не на весь блок памяти, выделяемый библиотекой DCRT. К счастью, в исходном коде библиотеки DCRT я нашел точный механизм выделе ния блоков памяти. Каждый блок памяти выделяется как структура _CrtMemBlock7 Header, определенная в файле DBGINT.H.
В файле DBGINT.H есть еще макросы доступа к _CrtMemBlockHeader через указа тель на данные пользователя и доступа к данным пользователя через указатель _CrtMemBlockHeader. Чтобы получить эту информацию, я скопировал структуру _CrtMemBlockHeader и макросы в заголовочный файл CRTDBG_INTERNALS.H (листинг 17 4). Создание копии определения структуры — не лучший метод, так как опре деление может измениться, но тут все в порядке, поскольку структура _CrtMemBlock7 Header не изменялась в библиотеке DCRT, начиная с Visual C++ 4, и все же это не значит, что она не изменится в будущих версиях Visual C++. Если вы собираетесь применять MemDumperValidator, вы должны будете следить за появлением всех пакетов обновлений и основных версий компилятора и проверять, не изменились ли в них внутренние структуры данных.
Листинг 17-4. CRTDBG_INTERNALS.H
/*——————————————————————————————————————————————————————————————————————
Отладка приложений для Microsoft .NET и Microsoft Windows Copyright (c) 199772003 John Robbins — All rights reserved.
——————————————————————————————————————————————————————————————————————*/
#ifndef _CRTDBG_INTERNALS_H #define _CRTDBG_INTERNALS_H #define nNoMansLandSize 4
typedef struct _CrtMemBlockHeader
{ |
|
|
struct _CrtMemBlockHeader * pBlockHeaderNext |
; |
|
struct _CrtMemBlockHeader * pBlockHeaderPrev |
; |
|
char * |
szFileName |
; |
int |
nLine |
; |