Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
При отладке программы MFC эти методы отладки могут оказаться полезными.
AfxDebugBreak
MFC предоставляет специальную функцию AfxDebugBreak для жёстко закодированных точек останова в исходном коде.
AfxDebugBreak( );
На платформах AfxDebugBreak
Intel создается следующий код, который нарушает исходный код, а не код ядра:
_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 можно использовать макрос DEBUG_NEW вместо нового оператора, чтобы найти утечки памяти. В отладочной версии вашей программы DEBUG_NEW
отслеживает имя файла и номер строки для каждого объекта, который она выделяет. При компиляции релизной версии программы DEBUG_NEW
сводится к простой операции new без информации о имени файла и номере строки. Таким образом, вы не платите штраф скорости в версии выпуска вашей программы.
Если вы не хотите переписать всю программу для использования DEBUG_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 при каждом выделении или освобождении памяти. Эти значения можно использовать в сочетании, выполнив логическую операцию OR, как показано ниже:
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
, или выделения примитивных типов C, таких как char
, int
, или long
. Если класс, производный от CObject, выделяет дополнительное пространство, например для внутренних буферов, эти объекты будут отображать как выделение объектов, так и неobject.
Предотвращение утечки памяти
Обратите внимание, что в приведенном выше коде блок памяти, связанный с 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 можно переопределить метод, чтобы предоставить дополнительную информацию при использовании 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 проекта, поэтому следует внести изменения, описанные в следующих шагах (а также выполнить "пересобрать всё", если требуется полная сборка Release).
В обозревателе решений выберите проект .
В меню "Вид " выберите "Страницы свойств".
Сначала вы создадите новую конфигурацию проекта.
В диалоговом окне "Страницы свойств проекта>" нажмите кнопку Configuration Manager.<
В диалоговом окне Configuration Manager найдите проект в сетке. В столбце "Конфигурация" нажмите кнопку< "Создать...>".
В диалоговом окне "Новая конфигурация проекта" введите имя новой конфигурации, например "Частичная отладка", в поле "Имя конфигурации проекта ".
В списке "Параметры копирования" выберите "Выпуск".
Нажмите кнопку "ОК" , чтобы закрыть диалоговое окно "Новая конфигурация проекта ".
Закройте диалоговое окно Configuration Manager .
Теперь вы задали параметры для всего проекта.
В диалоговом окне "Страницы свойств" в папке "Свойства конфигурации " выберите категорию "Общие ".
В сетке параметров проекта разверните параметры проекта по умолчанию (при необходимости).
В разделе "Проекты по умолчанию" найдите использование MFC. Текущий параметр отображается в правом столбце сетки. Щелкните текущий параметр и измените его на использование MFC в статической библиотеке.
В левой области диалогового окна "Страницы свойств" откройте папку C/C++ и выберите препроцессор. В сетке свойств найдите определения препроцессора и замените "NDEBUG" на "_DEBUG".
В левой области диалогового окна "Страницы свойств", откройте папку Компоновщик и выберите категорию Входные данные. В сетке свойств найдите дополнительные зависимости. В параметре "Дополнительные зависимости" введите NAFXCWD. LIB и LIBCMT.
Нажмите кнопку "ОК ", чтобы сохранить новые параметры сборки и закрыть диалоговое окно "Страницы свойств ".
В меню Сборка выберите Перестроить. Это удаляет все данные отладки из модулей, но не влияет на библиотеку MFC.
Теперь необходимо добавить отладочную информацию обратно в выбранные модули в приложении. Помните, что можно задать точки останова и выполнять другие функции отладчика только в модулях, скомпилированных с помощью сведений об отладке. Для каждого файла проекта, в котором требуется включить сведения об отладке, выполните следующие действия:
В обозревателе решений откройте папку "Исходные файлы" , расположенную в проекте.
Выберите файл, для которого нужно задать сведения об отладке.
В меню "Вид " выберите "Страницы свойств".
В диалоговом окне "Страницы свойств" в папке "Параметры конфигурации " откройте папку C/C++ и выберите категорию "Общие ".
В сетке свойств найдите формат сведений отладки.
Щелкните параметры формата отладочной информации и выберите нужный параметр (обычно /ZI) для отладочной информации.
Если вы используете приложение, созданное мастером, или имеете предварительно скомпилированные заголовки, необходимо отключить предварительно скомпилированные заголовки или перекомпилировать их перед компиляцией других модулей. В противном случае вы получите предупреждение C4650 и сообщение об ошибке C2855. Вы можете отключить предварительно скомпилированные заголовки, изменив параметр "Создание и использование предварительно скомпилированных заголовков" в <диалоговом окне "Свойства проекта>" (папка "Свойства конфигурации", вложенная папка C/C++, категория предварительно скомпилированных заголовков).
В меню "Сборка" выберите "Сборка ", чтобы перестроить файлы проекта, которые устарели.
В качестве альтернативы методу, описанному в этом разделе, можно использовать внешний файл makefile для определения отдельных параметров для каждого файла. В этом случае для связывания с библиотеками отладки MFC необходимо определить флаг _DEBUG для каждого модуля. Если вы хотите использовать библиотеки выпусков MFC, необходимо определить NDEBUG. Дополнительные сведения о написании внешних файлов makefile см. в справочнике NMAKE.