Использование DCRT-библиотеки
Первым шагом к применению DCRT-библиотеки является включение ее в проект, что позволяет немедленно начать извлекать выгоду из мониторинга памяти. Для этого перед директивами #inciude главного файла проекта (или любого другого файла вашего проекта) необходимо добавить следующую строку:
#define _CRTDBG_MAP_ALLOC
Потом к имеющимся в программе заголовочным файлам следует добавить файл CRTDBG.H. Определение _CRTDBG_MAP_ALLOC будет переадресовывать обращения к обычным функциям распределения и освобождения памяти к специальным версиям этих функций, которые будут записывать в отчет имя и номер строки исходного файла каждой операции распределения или освобождения памяти.
Затем необходимо включить являющийся частью DCRT-библиотеки код управления кучей. Как говорилось в начале этой главы, большинство свойств DCRT-библиотеки по умолчанию выключено. Документация утверждает, что это сделано для того, чтобы сохранять небольшой размер кода и увеличивать скорость его выполнения. Хотя размер и скорость могут быть важны для сборки релиза (release build, выпускного построения), основной целью отладочного построения (debug build) является поиск ошибок! При отладке увеличенный размер и уменьшенная скорость выполнения приложения не имеют особого значения. Поэтому без колебаний включайте все свойства, которые, по вашему мнению, могут оказаться полезными. Функция _CrtsetDbgFiag принимает набор флагов, перечисленных в табл. 15.2. Чтобы включать различные режимы библиотеки DCRT, их можно объединять друг с другом операцией ок.
После сборки приложения с указанными выше директивами #inciude и #define и вызова функции _crtsetDbgFlag обеспечен полный доступ к библиотеке DCRT, многочисленные функции которой помогут управлять использованием памяти и получать соответствующие отчеты. Эти функции можно вызывать в любой точке приложения, а многие из них использовать внутри утверждений, что позволяет отлавливать проблемы памяти вблизи источника.
Одна из наиболее полезных функций DCRT — _CrtcheckMemory.
Она просматривает всю распределенную память и проверяет, не записывались ли какие-нибудь данные в начало или в конец блока, а также не перераспределялись ли предварительно освобожденные блоки памяти. Только из-за одной этой функции применение библиотеки DCRT оправдано!
Другой набор функций позволяет проверять корректность данных любой области памяти. Функции _CrtIsValidHeapPointer, _CrtIsMemoryBlock И _crtisVaiidPointer удобно использовать в качестве отладочных параметров функций проверки корректности. Вместе с _crtcheckMemory эти функции являются превосходными средствами проверки памяти.
Таблица 15.2. Флажки библиотеки DCRT
Флажок
|
Описание
|
_CRTDBG_ALLOC_MEM_DF |
Включает процесс распределения отладочной кучи и использования идентификаторов блоков памяти. Это единственный флажок, который включен по умолчанию |
_CRTDBG_CHECK_ALWAYS_DF |
Контролирует и проверяет корректность всей памяти при каждом запросе на распределение и освобождение (памяти). Включение этого флажка отлавливает записи и перезаписи сразу же, как только они происходят |
_CRTDBG_CHECK_CRT_DF |
Включает распределение памяти для функций библиотеки CRT во все процедуры обнаружения утечек памяти и расхождений состояния. При отсутствии проблем с функциями библиотеки CRT не устанавливайте этот флажок, в противном случае вы будете получать сообщения о распределении памяти для CRT-библиотеки. Поскольку последняя должна сохранять некоторую память распределенной до момента действительного завершения программы, которое происходит уже после формирования отчетов об утечках памяти, то большинство сообщений об утечках памяти будут ложными |
CRTDBG_DELAY_FREE_MEM_DF |
Вместо действительного освобождения памяти сохранять распределенные блоки во внутреннем списке кучи. Блоки заполняются значениями OxDD, поэтому, просматривая этот список в отладчике, можно получить информацию об освобождении памяти. Этот флажок позволяет тестировать |
(прод.) |
программу с предельными требованиями к памяти, без реального ее освобождения. Кроме того, по заполненности списка значениями OxDD библиотека DCRT будет проверять, не пытались ли вы снова получить доступ к освобожденному блоку памяти. Нужно всегда включать этот флажок, но имейте в виду, что при этом требования вашей программы к памяти легко могут удвоиться, потому что освобожденная память не восстанавливается за счет кучи |
_CRTDBG_LEAK_CHECK_DF |
Проверять утечки памяти в конце программы. Включение этого чрезвычайно полезного флажка обязательно |
Обратите внимание еще на одну полезную группу функций в DCRT — это функции состояния памяти _CrtMemCheckpoint, _CrtMemDifference И _CrtMemDumpStatistics. Чтобы увидеть различные неполадки при работе кучи, эти функции полезно выполнять перед и после операцией сравнения областей кучи. Например, если используется обычный (не отладочный) вариант CRT-библиотеки, то можно сделать предварительные и последующие дампы кучи при вызове функций, сообщающих об утечке памяти или о размере памяти, используемой некоторой операцией.
Библиотека DCRT позволяет подключаться к потоку функций распределения и освобождения памяти, что помогает проследить каждый вызов этих функций. Если функция подключения к этому потоку возвращает значение TRUE, то распределение может продолжаться. Если же эта функция возвращает FALSE, то это означает, что был сбой в процессе распределения. Впервые обнаружив эти возможности, я подумал, что мог бы без особых усилий получить средства тестирования кода при некоторых действительно неприятных граничных условиях (которые в иной ситуации будет очень трудно дублировать). Результат можно увидеть в приложении MemStress (входящем в состав библиотеки BUGSLAYERUTIL.DLL). Эта программа, по существу, расширяет DCRT-библиотеку и позволяет форсировать отказы в процедурах распределения памяти (это приложение будет представлено в конце данной главы).
Кроме того, библиотека DCRT позволяет подключать функции дампов памяти и перечислять клиентские блоки (т. е. память, выделенную программе). Можно также заменять штатные функции дампов памяти собственными, которые "знают" все о ваших данных. Теперь, вместо просмотра загадочной выгруженной памяти (т. е. дампа), которую вы получаете по умолчанию (и которая, кроме того, что ее трудно расшифровывать, не так уж и полезна), можно получить точную информацию о содержимом блока памяти и вывести ее в удобном формате. Для этой цели в MFC имеется функция Dump, но она работает только с классами, производными от cobject. Если же вы (как и я) программируете не только в MFC, то вам нужны более общие функции дампов, приспособленные к различным типам кодов.
Свойство перечисления клиентских блоков, судя по названию, позволяет перечислять выделенные блоки памяти. Эта очень полезное свойство поможет создавать некоторые интересные утилиты. Например, в функциях MemDumperVaiidator из BUGSLAYERUTIL.DLL, я вызываю обработчики дампов из функции перечисления клиентских блоков, так что перечисление может выполнять дамп и проверку корректности многих типов распределенной памяти в одной операции. Это позволяет выполнять более глубокую проверку содержимого памяти (по сравнению с проверкой поверхности записей underwrites и overwrites). Под глубокой проверкой корректности я понимаю специальный алгоритм, которому известны форматы данных в блоке памяти и который, опираясь на знание этих форматов, гарантирует, что каждый элемент данных является корректным.