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


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

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

Содержание раздела

Функция AfxDebugBreak

Макрос TRACE

Обнаружение утечек памяти в 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 предоставляет классы и функции, позволяющие обнаруживать выделенную, но не освобожденную память.

7sx52ww7.collapse_all(ru-ru,VS.110).gifОтслеживание выделений памяти

В 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, как показано выше.

Содержание раздела

7sx52ww7.collapse_all(ru-ru,VS.110).gifВключение диагностики памяти

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

Включение или выключение диагностики памяти

  • Вызовите глобальную функцию AfxEnableMemoryTracking, чтобы включить или выключить выделение памяти с диагностикой.Поскольку диагностика памяти обычно включена по умолчанию в отладочной библиотеке, эта функция будет применяться для ее временного отключения — это позволит увеличить скорость выполнения программы и уменьшит вывод диагностических сообщений.

Выбор функции диагностики памяти с помощью afxMemDF

  • Если необходимо обеспечить более точное управление функциями диагностики памяти, можно задать значение глобальной переменной MFC afxMemDF, которая позволяет выборочно включать и отключать отдельные функции.Эта переменная может принимать следующие значения, заданные перечисляемым типом afxMemDF:

    Значение

    Описание

    allocMemDF

    Включает выделение памяти с диагностикой (по умолчанию).

    delayFreeMemDF

    Не будет задерживать освободить память при вызове delete или free до выходов программы.Это позволяет обеспечить выделение максимального объема памяти.

    checkAlwaysMemDF

    Вызывает AfxCheckMemory каждый раз при выделении или освобождении памяти.

    Эти значения можно комбинировать с помощью логической операции ИЛИ, как показано ниже:

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

Содержание раздела

7sx52ww7.collapse_all(ru-ru,VS.110).gifЕсли снимки памяти

  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 — для Просмотр статистики памяти, по которой можно найти конкретное место утечки.

Содержание раздела

7sx52ww7.collapse_all(ru-ru,VS.110).gifСтатистику памяти просмотра

Функция 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 показывает общее количество памяти, используемой программой.

Содержание раздела

7sx52ww7.collapse_all(ru-ru,VS.110).gifЕсли дампы объектов

В программе 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

Числа в фигурных скобках в начале большинства строк указывают порядок размещения объектов в памяти.Объект, размещенный в последнюю очередь, имеет наибольший номер и появляется вверху дампа.

Чтобы получить максимальный объем сведения из дампа объекта можно переопределить функцию-член любого производный от CObject объект Dump настраивать дампа объекта.

Можно установить точку останова на определенном выделении памяти, задав глобальной переменной _afxBreakAlloc значение, показанное в фигурных скобках.При перезапуске программы отладчик остановит выполнение именно там, где происходит это выделение памяти.Теперь можно посмотреть стек вызовов и проанализировать, как программа дошла до этого места.

В библиотеке времени выполнения языка C тоже есть подобная функция, _CrtSetBreakAlloc, которая применяется для выделений памяти во время выполнения в языке C.

Содержание раздела

7sx52ww7.collapse_all(ru-ru,VS.110).gifИнтерпретация дампы памяти

Рассмотрим этот дамп объекта более подробно:

{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;
}

Содержание раздела

7sx52ww7.collapse_all(ru-ru,VS.110).gifНастраивать дампы объектов

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

Содержание раздела

7sx52ww7.collapse_all(ru-ru,VS.110).gifСоздание приложения MFC с отладочной информации для избранных модулей

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

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

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

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

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

    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.

Содержание раздела

См. также

Другие ресурсы

Отладка Visual C++