Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

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

.pdf
Скачиваний:
353
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

ГЛАВА 17 Стандартная отладочная библиотека C и управление памятью

623

 

 

 

 

 

 

/*——————————————————————————————————————————————————————————————————————

 

ФУНКЦИЯ: ValidateAllBlocks

 

ОПИСАНИЕ:

 

Проверяет всю память, выделенную из локальной кучи.

 

Просматривает все клиентские блоки и вызывает индивидуальные

 

функции проверки для их различных подтипов.

 

Вероятно, лучше всего вызывать эту функцию при помощи

 

макроса VALIDATEALLBLOCKS, описанного ниже.

 

ПАРАМЕТРЫ:

 

pContext – указатель на информацию о контексте,

 

передаваемый в каждую функцию проверки.

 

ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ:

 

Нет

 

——————————————————————————————————————————————————————————————————————*/

 

BUGSUTIL_DLLINTERFACE void __stdcall

 

ValidateAllBlocks ( void * pContext ) ;

 

#ifdef __cplusplus

////////////////////////////////////////////////////////////////////////

//Вспомогательные макросы для классов C++

////////////////////////////////////////////////////////////////////////

//Объявляйте этот макрос в своем классе так же, как макросы MFC.

#define DECLARE_MEMDEBUG(classname)

 

 

 

\

public

:

 

 

 

\

static BSMDVINFO m_stBSMDVINFO ;

 

 

 

\

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_stBSMDVINFO.dwValue

)

 

\

 

{

 

 

 

\

 

m_stBSMDVINFO.pfnDump

=

classname::ClassDumper ;

\

 

m_stBSMDVINFO.pfnValidate =

classname::ClassValidator ; \

 

AddClientDV ( &m_stBSMDVINFO ) ;

 

\

 

}

 

 

 

\

 

return ( _malloc_dbg ( nSize

 

 

,

\

 

(int)m_stBSMDVINFO.dwValue ,

\

 

__FILE__

 

,

\

 

__LINE__

 

) ) ;

\

}

 

 

 

 

\

static void * operator new ( size_t

nSize

,

\

 

char *

lpszFileName ,

\

 

int

 

nLine

)

\

{

 

 

 

 

\

 

if ( 0 == m_stBSMDVINFO.dwValue

)

 

\

 

{

 

 

 

\

 

m_stBSMDVINFO.pfnDump

=

classname::ClassDumper ;

\

 

m_stBSMDVINFO.pfnValidate =

classname::ClassValidator ; \

 

AddClientDV ( &m_stBSMDVINFO ) ;

 

\

см. след. стр.

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

ГЛАВА 17 Стандартная отладочная библиотека C и управление памятью

625

 

 

#define MEMDEBUG_NEW new ( __FILE__ , __LINE__ ) #endif

#endif

// Конец директивы #ifdef __cplusplus.

////////////////////////////////////////////////////////////////////////

//Вспомогательные макросы C

////////////////////////////////////////////////////////////////////////

//Используйте этот макрос для выделения памяти в стиле C.

//Единственная проблема с языком C заключается в том,

//что вам придется возиться со структурой BSMDVINFO.

#define INITIALIZE_MEMDEBUG(lpBSMDVINFO , pfnD , pfnV )

\

{

\

ASSERT ( FALSE == IsBadWritePtr ( lpBSMDVINFO ,

\

sizeof ( BSMDVINFO ) ) ) ;

\

((LPBSMDVINFO)lpBSMDVINFO)7>dwValue = 0 ;

\

((LPBSMDVINFO)lpBSMDVINFO)7>pfnDump = pfnD ;

\

((LPBSMDVINFO)lpBSMDVINFO)7>pfnValidate = pfnV ;

\

AddClientDV ( lpBSMDVINFO ) ;

\

}

 

//Макросы, сответствующие функциям выделения памяти в стиле C.

//С ними будет легче работать, если создать для них оболочки,

//позволяющие не запоминать, какие структуры BSMDVINFO

//передавать в каждую функцию.

#define MEMDEBUG_MALLOC(lpBSMDVINFO , nSize)

\

_malloc_dbg (

nSize

,

\

 

((LPBSMDVINFO)lpBSMDVINFO)7>dwValue ,

\

 

__FILE__

,

\

 

__LINE__

)

 

#define MEMDEBUG_REALLOC(lpBSMDVINFO , pBlock , nSize)

\

_realloc_dbg(

pBlock

,

\

 

nSize

,

\

 

((LPBSMDVINFO)lpBSMDVINFO)7>dwValue

, \

 

__FILE__

,

\

 

__LINE__

)

 

#define MEMDEBUG_EXPAND(lpBSMDVINFO , pBlock , nSize )

\

_expand_dbg( pBlock

,

\

 

nSize

,

\

 

((LPBSMDVINFO)lpBSMDVINFO)7>dwValue ,

\

 

__FILE__

,

\

 

__LINE__

)

 

#define

MEMDEBUG_FREE(lpBSMDVINFO

, pBlock)

\

 

_free_dbg ( pBlock

,

\

 

((LPBSMDVINFO)lpBSMDVINFO)7>dwValue )

#define

MEMDEBUG_MSIZE(lpBSMDVINFO , pBlock)

\

 

_msize_dbg ( pBlock ,

((LPBSMDVINFO)lpBSMDVINFO)7>dwValue )

// Макрос для вызова функции ValidateAllBlocks.

см. след. стр.

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++, применение функ ции проверки памяти в обоих языках одинаково. Для этого нужно только вызвать

ГЛАВА 17 Стандартная отладочная библиотека C и управление памятью

629

 

 

макрос VALIDATEALLBLOCKS. Он расширяется в отладочных компоновках в вызов фун кции ValidateAllBlocks. Параметром макроса может быть любое значение, кото рое вы хотите передать зарегистрированным функциям проверки памяти. Рань ше я указывал через этот параметр глубину выполняемой функцией проверки. Помните: ValidateAllBlocks передает это значение каждой зарегистрированной функции проверки, поэтому вам нужно согласовать его между всеми членами вашей группы.

Чтобы увидеть функции расширения MemDumperValidator в действии, изучи те программу Dump (листинг 17 3; каталог BUGSLAYERUTIL\TESTS\DUMP на CD). Dump демонстрирует все действия, нужные для использования расширения. Хотя я не привел соответствующего кода, расширение MemDumperValidator хорошо работает с MFC, так как MFC вызывает любую зарегистрированную клиентскую функцию ловушку записи дампа. MemDumperValidator позволяет вам получить в свое распоряжение лучшее обоих миров!

Листинг 17-3. DUMP.CPP

/*——————————————————————————————————————————————————————————————————————

Отладка приложений для Microsoft .NET и Microsoft Windows Copyright © 199772003 John Robbins — All rights reserved.

——————————————————————————————————————————————————————————————————————*/ #include <stdio.h>

#include <stdlib.h> #include <memory.h> #include <string.h> #include <tchar.h> #include "BugslayerUtil.h"

#pragma push_macro ( "new" ) #ifdef new

#undef new #endif

class TestClass

{

public:

TestClass ( void )

{

_tcscpy ( m_szData , _T ( "TestClass constructor data!" ) ) ;

}

~TestClass ( void )

{

m_szData[ 0 ] = _T ( '\0' ) ;

}

// Объявления механизмов отладки памяти для классов C++. DECLARE_MEMDEBUG ( TestClass ) ;

private :

TCHAR m_szData[ 100 ] ;

см. след. стр.

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 )

ГЛАВА 17 Стандартная отладочная библиотека C и управление памятью

631

 

 

{

_RPT2 ( _CRT_WARN

 

 

,

"

Data is

Name

: %S\n"

 

"

 

Rank

: %S\n"

,

((SimpleStruct*)pData)7>szName

,

((SimpleStruct*)pData)7>szRank

) ;

}

void ValidatorTwo ( const void * pData , const void * /*pContext*/ )

{

// Выполняйте здесь проверку полей структур.

_RPT2 ( _CRT_WARN ,

"Validator called with :\n"

"Data is Name : %s\n"

"

Rank : %s\n"

,

((SimpleStruct*)pData)7>szName

,

((SimpleStruct*)pData)7>szRank

) ;

}

//К сожалению, функции C нуждаются в собственных структурах

//BSMDVINFO. При работе над реальными программами вам следует

//определять эти структуры как внешние ссылки и создавать

//для макросов MEMDEBUG собственные макросы7оболочки.

static BSMDVINFO g_dvOne ; static BSMDVINFO g_dvTwo ;

void main ( void )

{

_tprintf ( _T ( "At start of main\n" ) ) ;

//Инициализация механизмов отладки памяти для типа 1. INITIALIZE_MEMDEBUG ( &g_dvOne , DumperOne , ValidatorOne ) ;

//Инициализация механизмов отладки памяти для типа 2. INITIALIZE_MEMDEBUG ( &g_dvTwo , DumperTwo , ValidatorTwo ) ;

//Выделение памяти для объекта C++ при помощи

//оператора new, определенного в макросе MEMDEBUG.

TestClass * pstClass ; pstClass = new TestClass ( ) ;

// Выделение памяти для двух типов C.

TCHAR * p = (TCHAR*)MEMDEBUG_MALLOC ( &g_dvOne , 20 ) ; _tcscpy ( p , _T ( "VC VC" ) ) ;

SimpleStruct * pSt =

(SimpleStruct*)MEMDEBUG_MALLOC ( &g_dvTwo ,

sizeof ( SimpleStruct ) ) ;

_tcscpy ( pSt7>szName , _T ( "Pam" ) ) ; _tcscpy ( pSt7>szRank , _T ( "CINC" ) ) ;

см. след. стр.

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

;

Соседние файлы в предмете Программирование на C++