Методы отладки MFC
Эти методы могут пригодиться при отладке программы MFC.
В этом разделе
Обнаружение утечек памяти в MFC
Функция AfxDebugBreak
MFC предоставляет особую функцию AfxDebugBreak для жесткого задания точек останова в исходном коде:
AfxDebugBreak( );
На платформах Intel AfxDebugBreak
создает следующий код, останавливающий выполнение исходного кода, а не кода ядра:
_asm int 3
На других платформах AfxDebugBreak
просто вызывает DebugBreak
.
Не забывайте удалять AfxDebugBreak
при создании окончательного построения или используйте #ifdef _DEBUG
до и после этих операторов.
Макрос TRACE
Чтобы сообщения программы отображались в окне "Вывод"отладчика, можно применить макрос ATLTRACE или MFC-макрос TRACE . Подобно утверждениям, макросы трассировки активны только в отладочной версии программы, а в окончательной версии они исчезают после компиляции.
Следующие примеры показывают несколько способов применения макроса TRACE . Подобно printf
макрос TRACE может обрабатывать несколько аргументов.
int x = 1;
int y = 16;
float z = 32.0;
TRACE( "This is a TRACE statement\n" );
TRACE( "The value of x is %d\n", x );
TRACE( "x = %d and y = %d\n", x, y );
TRACE( "x = %d and y = %x and z = %f\n", x, y, z );
Макрос TRACE правильно обрабатывает параметры char* и wchar_t*. Следующие примеры демонстрируют использование макро TRACE вместе с различными типами строковых параметров.
TRACE( "This is a test of the TRACE macro that uses an ANSI string: %s %d\n", "The number is:", 2);
TRACE( L"This is a test of the TRACE macro that uses a UNICODE string: %s %d\n", L"The number is:", 2);
TRACE( _T("This is a test of the TRACE macro that uses a TCHAR string: %s %d\n"), _T("The number is:"), 2);
Дополнительные сведения о макросе TRACE см. в разделе Службы диагностики.
Обнаружение утечек памяти в MFC
MFC предоставляет классы и функции, позволяющие обнаруживать выделенную, но не освобожденную память.
Отслеживание операций выделения памяти
В MFC вместо оператора new для обнаружения утечек памяти можно применять макрос DEBUG_NEW . В отладочной версии программы DEBUG_NEW
отслеживает имя файла и номер строки для каждого объекта, которому выделяется память. При компиляции окончательной версии программы DEBUG_NEW
становится простой операцией new без данных об имени файла и номере строки. Таким образом, окончательная версия программы выполняется с необходимой скоростью.
Чтобы не переписывать программу, используя DEBUG_NEW
вместо new, можно в исходных файлах определить данный макрос:
#define new DEBUG_NEW
Если создается дамп объекта, каждый объект, память для которого выделяется с помощью DEBUG_NEW
, показывает файл и номер строки, где было выполнено выделение, позволяя точнее выявить источник утечки памяти.
В отладочной версии структуры MFC макрос DEBUG_NEW
используется автоматически, но в коде, разумеется, нет. Если же требуется воспользоваться преимуществами DEBUG_NEW
, то нужно явно указать DEBUG_NEW
или #define new , как показано выше.
Включение диагностики памяти
Прежде чем воспользоваться возможностями диагностики памяти, нужно включить диагностическую трассировку.
Включение или выключение диагностики памяти
Вызовите глобальную функцию AfxEnableMemoryTracking , чтобы включить или выключить выделение памяти с диагностикой. Поскольку диагностика памяти обычно включена по умолчанию в отладочной библиотеке, эта функция будет применяться для ее временного отключения — это позволит увеличить скорость выполнения программы и уменьшит вывод диагностических сообщений.
Выбор функции диагностики памяти с помощью afxMemDF
Если необходимо обеспечить более точное управление функциями диагностики памяти, можно задать значение глобальной переменной MFC afxMemDF, которая позволяет выборочно включать и отключать отдельные функции. Эта переменная может принимать следующие значения, заданные перечисляемым типом afxMemDF:
значение Описание allocMemDF Включает выделение памяти с диагностикой (по умолчанию). delayFreeMemDF Задерживает освобождение памяти до выхода из программы при вызове delete
илиfree
. Это позволяет обеспечить выделение максимального объема памяти.checkAlwaysMemDF Вызывает AfxCheckMemory каждый раз при выделении или освобождении памяти. Эти значения можно комбинировать с помощью логической операции ИЛИ, как показано ниже:
afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
Получение снимков памяти
Создайте объект CMemoryState и вызовите функцию-член CMemoryState::Checkpoint . В результате будет создан первый снимок памяти.
После того как программа выполнит операцию по выделению или освобождению памяти, создайте другой объект
CMemoryState
и вызовите функциюCheckpoint
уже для него. Так получится второй снимок памяти.Создайте третий объект
CMemoryState
и вызовите его функцию-член CMemoryState::Difference , используя в качестве аргументов два предыдущих объектаCMemoryState
. Если между двумя состояниями памяти есть различия, функцияDifference
вернет отличное от нуля значение. Это значение будет свидетельствовать о наличии неосвобожденных блоков памяти.Пример кода выглядит следующим образом:
// Declare the variables needed #ifdef _DEBUG CMemoryState oldMemState, newMemState, diffMemState; oldMemState.Checkpoint(); #endif // Do your memory allocations and deallocations. CString s("This is a frame variable"); // The next object is a heap object. CPerson* p = new CPerson( "Smith", "Alan", "581-0215" ); #ifdef _DEBUG newMemState.Checkpoint(); if( diffMemState.Difference( oldMemState, newMemState ) ) { TRACE( "Memory leaked!\n" ); } #endif
Обратите внимание, что операторы проверки памяти заключаются в блоки #ifdef _DEBUG / #endif, поэтому они компилируются только в отладочных версиях программы.
Теперь, когда известно о наличии утечки, можно применить другую функцию-член — CMemoryState::DumpStatistics , по которой можно найти конкретное место утечки.
Просмотр статистики памяти
Функция CMemoryState::Difference просматривает два объекта-состояния памяти и определяет, какие объекты не были освобождены из кучи между начальным и конечным состоянием. После того как сделаны снимки памяти и выполнено их сравнение с помощью функции CMemoryState::Difference
, можно вызвать функцию CMemoryState::DumpStatistics и получить сведения о неосвобожденных объектах.
Рассмотрим следующий пример:
if( diffMemState.Difference( oldMemState, newMemState ) )
{
TRACE( "Memory leaked!\n" );
diffMemState.DumpStatistics();
}
Образец дампа из примера выглядит следующим образом:
0 bytes in 0 Free Blocks
22 bytes in 1 Object Blocks
45 bytes in 4 Non-Object Blocks
Largest number used: 67 bytes
Total allocations: 67 bytes
Свободные блоки — это блоки, освобождение которых задерживается, если afxMemDF
была установлена в delayFreeMemDF
.
Обычные блоки объектов, показанные во второй строке, остаются выделенными в куче.
Блоки без объектов включают в себя массивы и структуры, созданные с помощью new
. В этом случае четыре блока без объектов были созданы в куче, но не освобождены.
Largest number used
показывает наибольшее количество памяти, используемой программой в любое время.
Total allocations
показывает общее количество памяти, используемой программой.
Получение дампов объектов
В программе MFC можно использовать функцию CMemoryState::DumpAllObjectsSince для помещения в дамп описания всех объектов кучи, которые не были освобождены. DumpAllObjectsSince
помещает в дамп все объекты, размещенные с момента последней проверки состояния памяти CMemoryState::Checkpoint. Если вызова Checkpoint
не было, DumpAllObjectsSince
отображает все объекты и не-объекты, находящиеся в памяти на данный момент.
Примечание.
Перед тем как использовать функцию создания дампа объектов MFC, необходимо включить диагностическую трассировку.
Примечание.
MFC автоматически отображает все потерянные объекты при выходе из программы, поэтому в точке выхода не нужно создавать для этого дополнительный код.
Следующий код — тест на утечку памяти путем сравнения двух состояний памяти и отображения всех объектов, если таковая обнаружилась.
if( diffMemState.Difference( oldMemState, newMemState ) )
{
TRACE( "Memory leaked!\n" );
diffMemState.DumpAllObjectsSince();
}
Содержимое дампа выглядит следующим образом:
Dumping objects ->
{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4
Last Name: Smith
First Name: Alan
Phone #: 581-0215
{1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long
Числа в фигурных скобках в начале большинства строк указывают порядок размещения объектов в памяти. Объект, размещенный в последнюю очередь, имеет наибольший номер и появляется вверху дампа.
Чтобы извлечь из дампа объекта максимальное количество сведений, можно переопределить функцию-член Dump
любого объекта, производного от CObject
, чтобы настроить дамп объекта оптимальным образом.
Можно установить точку останова на определенном выделении памяти, задав глобальной переменной _afxBreakAlloc
значение, показанное в фигурных скобках. При перезапуске программы отладчик остановит выполнение именно там, где происходит это выделение памяти. Теперь можно посмотреть стек вызовов и проанализировать, как программа дошла до этого места.
В библиотеке времени выполнения языка C тоже есть подобная функция, _CrtSetBreakAlloc, которая применяется для выделений памяти во время выполнения в языке C.
Интерпретация дампов памяти
Рассмотрим этот дамп объекта более подробно:
{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4
Last Name: Smith
First Name: Alan
Phone #: 581-0215
{1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long
Программа, которая создала этот дамп, выделяла память явным образом только дважды — один раз в стеке, а другой раз в куче:
// Do your memory allocations and deallocations.
CString s("This is a frame variable");
// The next object is a heap object.
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
CPerson
получает три аргумента — указатели на тип char
, используемые для инициализации переменных-членов CString
. В дампе памяти объект CPerson
размещается вместе с тремя не-объектными блоками (3, 4 и 5). Они содержат знаки для переменных-членов CString
и не будут удалены в случае вызова деструктора объекта CPerson
.
Блок номер 2 — собственно объект CPerson
. $51A4
представляет собой адрес блока, за которым следует содержимое объекта, выводимое CPerson
::Dump
при вызове с помощью DumpAllObjectsSince.
Нетрудно догадаться, что блок номер 1 связан с переменной фрейма CString
, это видно из порядкового номера и размера, соответствующего количеству знаков в переменной фрейма CString
. Переменные, размещенные во фрейме, автоматически освобождаются, когда фрейм выходит за пределы области действия.
Переменные фрейма
В основном можно не волноваться по поводу объектов кучи, связанных с переменными фрейма, потому что они автоматически освобождаются, когда эти переменные выходят за пределы области действия. Чтобы избежать беспорядка в диагностическом дампе, следует располагать вызовы Checkpoint
так, чтобы они находились вне области действия переменных фрейма. Например, поместите фигурные скобки так, чтобы они окружали код предыдущего выделения памяти:
oldMemState.Checkpoint();
{
// Do your memory allocations and deallocations ...
CString s("This is a frame variable");
// The next object is a heap object.
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
}
newMemState.Checkpoint();
С квадратными скобками в этих местах дамп памяти для этого примера выглядит следующим образом:
Dumping objects ->
{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4
Last Name: Smith
First Name: Alan
Phone #: 581-0215
Не-объектные выделения
Следует отметить, что выделения бывают "объектными" (например, CPerson
) и "не-объектными". Необъектные выделения — это выделения для объектов, которые не являются производными от CObject
, а также выделения простых типов языка С, таких как char
, int
или long
. Если класс, производный от CObject, выделяет дополнительное пространство, например внутренний буфер, такие объекты отобразят и объектное, и не-объектное выделение.
Предотвращение утечек памяти
Заметьте: в приведенном выше коде блок памяти, связанный с переменной фрейма CString
, был освобожден автоматически и не отобразился как утечка. Автоматическое освобождение, обусловленное правилами ограничения области видимости, предотвращает большинство утечек памяти, связанных с переменными фрейма.
Однако для объектов, распределенных в куче, во избежание утечки памяти нужно явно удалять объект. Чтобы очистить последнюю утечку памяти в предыдущем примере, удалите объект CPerson
, размещенный в куче:
{
// Do your memory allocations and deallocations.
CString s("This is a frame variable");
// The next object is a heap object.
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
delete p;
}
Настройка дампов объектов
Если класс наследуется от CObject, можно переопределить функцию-член Dump
, чтобы получить больше сведений при использовании DumpAllObjectsSince для вывода объектов в Окне вывода.
Функция Dump
записывает текстовое представление переменных-членов объекта в контекст дампа (CDumpContext). Контекст дампа подобен потоку ввода-вывода. Для отправки данных в<<<< CDumpContext
.
При переопределении функции Dump
следует вначале вызвать версию Dump
базового класса для вывода содержимого объектов базового класса. Затем вывести текстовое описание и значение для каждой переменной-члена производного класса.
Объявление функции Dump
выглядит следующим образом:
class CPerson : public CObject
{
public:
#ifdef _DEBUG
virtual void Dump( CDumpContext& dc ) const;
#endif
CString m_firstName;
CString m_lastName;
// And so on...
};
Поскольку формирование дампа объекта имеет смысл только при отладке программы, объявление функции Dump
заключается в блок #ifdef _DEBUG / #endif .
В следующем примере функция Dump
сначала вызывает функцию Dump
для базового класса. Затем пишет короткое описание каждой переменной-члена и вместе со значением этой переменной направляет его в диагностический поток.
#ifdef _DEBUG
void CPerson::Dump( CDumpContext& dc ) const
{
// Call the base class function first.
CObject::Dump( dc );
// Now do the stuff for our specific class.
dc << "last name: " << m_lastName << "\n"
<< "first name: " << m_firstName << "\n";
}
#endif
Для указания, куда будут направлены выходные данные дампа, следует применить аргумент CDumpContext
. Отладочная версия MFC использует предопределенный объект CDumpContext
с именем afxDump
, который направляет выходные данные в отладчик.
CPerson* pMyPerson = new CPerson;
// Set some fields of the CPerson object.
//...
// Now dump the contents.
#ifdef _DEBUG
pMyPerson->Dump( afxDump );
#endif
Сокращение размера отладочной сборки MFC
Отладочная информация для большого MFC-приложения может занимать значительное дисковое пространство. Для уменьшения размера можно использовать одну из описанных ниже процедур.
Перестройте библиотеки MFC, используя параметр /Z7, /Zi, /ZI (формат отладочной информации) вместо /Z7. С помощью этих параметров строится один файл программной базы данных (PDB), содержащий отладочную информацию для всей библиотеки, тем самым сохраняется место на диске.
Перестройте библиотеки MFC, не указывая отладочную информацию (не используя параметр /Z7, /Zi, /ZI (формат отладочной информации)). В этом случае из-за отсутствия отладочной информации использовать большинство возможностей отладчика внутри кода библиотеки MFC не удастся, но, так как библиотеки MFC уже отлажены, это не будет проблемой.
Соберите собственное приложение с отладочной информацией только для избранных модулей, используя описанную ниже процедуру.
Сборка приложения MFC с отладочной информацией для избранных модулей
Построение избранных модулей с отладочными библиотеками MFC позволяет использовать пошаговое выполнение и другие отладочные функции в этих модулях. Эта процедура использует конфигурации Debug и Release для проекта, таким образом создавая необходимость изменений, описанных ниже (и также вынуждая "перестроить все", когда потребуется построение окончательной версии).
В обозревателе решений выберите проект .
В меню Вид выберите Страницы свойств.
Сначала создайте новую конфигурацию проекта.
В диалоговом окне Страницы свойств <Project> нажмите кнопку Диспетчер конфигураций.
В диалоговом окне Диспетчер конфигурацийнайдите нужный проект в таблице. В столбце Конфигурация выберите <Создать...>.
В диалоговом окне Создание конфигурации проектавведите имя новой конфигурации, например, "Неполная отладка" в поле Имя конфигурации проекта .
В списке Копировать параметры из выберите Выпуск.
Нажмите кнопку ОК, чтобы закрыть диалоговое окно Создание конфигурации проекта.
Закройте диалоговое окно Диспетчер конфигураций .
Теперь нужно настроить параметры для всего проекта.
В диалоговом окне Страницы свойств в папке Свойства конфигурации выберите категорию Общие .
В таблице параметров проекта разверните Параметры проекта по умолчанию (если нужно).
В Параметрах проекта по умолчаниюнайдите Использовать MFC. В правом столбце таблицы появится текущее значение параметра. Измените его на Использовать MFC в статической библиотеке.
В левой области диалогового окна Страницы свойств откройте папку C/C++ и выберите Препроцессор. В таблице свойств найдите Определения препроцессора и замените NDEBUG на _DEBUG.
В левой области диалогового окна Страницы свойств откройте папку Компоновщик и выберите категорию Ввод . В таблице свойств найдите Дополнительные зависимости. Для свойства Дополнительные зависимости введите NAFXCWD.LIB и LIBCMT.
Нажмите OK , чтобы сохранить новые параметры построения, и закройте диалоговое окно Страницы свойств .
Из меню Построение выберите подпункт Перестроить. Это действие удалит всю отладочную информацию из модулей, но не затронет библиотеку MFC.
Теперь нужно добавить отладочную информацию в избранные модули приложения. Помните, что можно задавать точки останова и выполнять другие отладочные действия только в тех модулях, которые скомпилированы с отладочной информацией. Для каждого файла проекта, в который нужно включить отладочную информацию, проделайте следующие действия:
В обозревателе решений откройте папку Исходные файлы , расположенную в проекте.
Выберите файл, для которого нужно настроить отладочную информацию.
В меню Вид выберите Страницы свойств.
В диалоговом окне Страницы свойств в папке Параметры конфигурации откройте папку C/C++ и выберите категорию Общие .
В таблице свойств найдите Формат отладочной информации.
Щелкните Формат отладочной информации и выберите нужный параметр (обычно /ZI.
Если приложение создано с использованием мастера создания приложений или имеет предкомпилированные заголовки, следует эти заголовки выключить или перекомпилировать их перед компиляцией остальных модулей. Иначе будет получено предупреждение C4650 и сообщение об ошибке C2855. Предкомпилированные заголовки можно отключить, изменив параметр Создать/Использовать предкомпилированные заголовки в диалоговом окне Свойства <Проект> (папка Свойства конфигурации, подпапка C/C++, категория Предкомпилированные заголовки).
В меню Построение выберите Построить для перестройки устаревших файлов проекта.
Как альтернативу описанному здесь способу для настройки отдельных параметров каждого файла можно использовать внешний сборочный файл проекта. В этом случае помните: чтобы подключить отладочные библиотеки MFC, следует определить флаг _DEBUG для каждого модуля. Если необходимо использовать конечную версию библиотеки MFC, нужно определить NDEBUG. Дополнительные сведения о создании внешних сборочных файлов проекта см. в Справочнике NMAKE.