Поделиться через


Методы отладки MFC

Эти методы могут пригодиться при отладке программы MFC.

В этом разделе

Функция AfxDebugBreak

Макрос TRACE

Обнаружение утечек памяти в 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;
    

    В этом разделе

Получение снимков памяти

  1. Создайте объект CMemoryState и вызовите функцию-член CMemoryState::Checkpoint . В результате будет создан первый снимок памяти.

  2. После того как программа выполнит операцию по выделению или освобождению памяти, создайте другой объект CMemoryState и вызовите функцию Checkpoint уже для него. Так получится второй снимок памяти.

  3. Создайте третий объект 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-приложения может занимать значительное дисковое пространство. Для уменьшения размера можно использовать одну из описанных ниже процедур.

  1. Перестройте библиотеки MFC, используя параметр /Z7, /Zi, /ZI (формат отладочной информации) вместо /Z7. С помощью этих параметров строится один файл программной базы данных (PDB), содержащий отладочную информацию для всей библиотеки, тем самым сохраняется место на диске.

  2. Перестройте библиотеки MFC, не указывая отладочную информацию (не используя параметр /Z7, /Zi, /ZI (формат отладочной информации)). В этом случае из-за отсутствия отладочной информации использовать большинство возможностей отладчика внутри кода библиотеки MFC не удастся, но, так как библиотеки MFC уже отлажены, это не будет проблемой.

  3. Соберите собственное приложение с отладочной информацией только для избранных модулей, используя описанную ниже процедуру.

    В этом разделе

Сборка приложения MFC с отладочной информацией для избранных модулей

Построение избранных модулей с отладочными библиотеками MFC позволяет использовать пошаговое выполнение и другие отладочные функции в этих модулях. Эта процедура использует конфигурации Debug и Release для проекта, таким образом создавая необходимость изменений, описанных ниже (и также вынуждая "перестроить все", когда потребуется построение окончательной версии).

  1. В обозревателе решений выберите проект .

  2. В меню Вид выберите Страницы свойств.

  3. Сначала создайте новую конфигурацию проекта.

    1. В диалоговом окне Страницы свойств <Project> нажмите кнопку Диспетчер конфигураций.

    2. В диалоговом окне Диспетчер конфигурацийнайдите нужный проект в таблице. В столбце Конфигурация выберите <Создать...>.

    3. В диалоговом окне Создание конфигурации проектавведите имя новой конфигурации, например, "Неполная отладка" в поле Имя конфигурации проекта .

    4. В списке Копировать параметры из выберите Выпуск.

    5. Нажмите кнопку ОК, чтобы закрыть диалоговое окно Создание конфигурации проекта.

    6. Закройте диалоговое окно Диспетчер конфигураций .

  4. Теперь нужно настроить параметры для всего проекта.

    1. В диалоговом окне Страницы свойств в папке Свойства конфигурации выберите категорию Общие .

    2. В таблице параметров проекта разверните Параметры проекта по умолчанию (если нужно).

    3. В Параметрах проекта по умолчаниюнайдите Использовать MFC. В правом столбце таблицы появится текущее значение параметра. Измените его на Использовать MFC в статической библиотеке.

    4. В левой области диалогового окна Страницы свойств откройте папку C/C++ и выберите Препроцессор. В таблице свойств найдите Определения препроцессора и замените NDEBUG на _DEBUG.

    5. В левой области диалогового окна Страницы свойств откройте папку Компоновщик и выберите категорию Ввод . В таблице свойств найдите Дополнительные зависимости. Для свойства Дополнительные зависимости введите NAFXCWD.LIB и LIBCMT.

    6. Нажмите OK , чтобы сохранить новые параметры построения, и закройте диалоговое окно Страницы свойств .

  5. Из меню Построение выберите подпункт Перестроить. Это действие удалит всю отладочную информацию из модулей, но не затронет библиотеку MFC.

  6. Теперь нужно добавить отладочную информацию в избранные модули приложения. Помните, что можно задавать точки останова и выполнять другие отладочные действия только в тех модулях, которые скомпилированы с отладочной информацией. Для каждого файла проекта, в который нужно включить отладочную информацию, проделайте следующие действия:

    1. В обозревателе решений откройте папку Исходные файлы , расположенную в проекте.

    2. Выберите файл, для которого нужно настроить отладочную информацию.

    3. В меню Вид выберите Страницы свойств.

    4. В диалоговом окне Страницы свойств в папке Параметры конфигурации откройте папку C/C++ и выберите категорию Общие .

    5. В таблице свойств найдите Формат отладочной информации.

    6. Щелкните Формат отладочной информации и выберите нужный параметр (обычно /ZI.

    7. Если приложение создано с использованием мастера создания приложений или имеет предкомпилированные заголовки, следует эти заголовки выключить или перекомпилировать их перед компиляцией остальных модулей. Иначе будет получено предупреждение C4650 и сообщение об ошибке C2855. Предкомпилированные заголовки можно отключить, изменив параметр Создать/Использовать предкомпилированные заголовки в диалоговом окне Свойства <Проект> (папка Свойства конфигурации, подпапка C/C++, категория Предкомпилированные заголовки).

  7. В меню Построение выберите Построить для перестройки устаревших файлов проекта.

    Как альтернативу описанному здесь способу для настройки отдельных параметров каждого файла можно использовать внешний сборочный файл проекта. В этом случае помните: чтобы подключить отладочные библиотеки MFC, следует определить флаг _DEBUG для каждого модуля. Если необходимо использовать конечную версию библиотеки MFC, нужно определить NDEBUG. Дополнительные сведения о создании внешних сборочных файлов проекта см. в Справочнике NMAKE.

    В этом разделе