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


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

При отладке программы 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;
    

Создание моментальных снимков памяти

  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, или выделения примитивных типов 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 могут занять много места на диске. Для уменьшения размера можно использовать одну из следующих процедур:

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

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

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

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

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

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

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

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

    1. В диалоговом окне "Страницы свойств проекта>" нажмите кнопку Configuration Manager.<

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

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

    4. В списке "Параметры копирования" выберите "Выпуск".

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

    6. Закройте диалоговое окно Configuration Manager .

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

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

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

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

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

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

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

  5. В меню Сборка выберите Перестроить. Это удаляет все данные отладки из модулей, но не влияет на библиотеку MFC.

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

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

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

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

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

    5. В сетке свойств найдите формат сведений отладки.

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

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

  7. В меню "Сборка" выберите "Сборка ", чтобы перестроить файлы проекта, которые устарели.

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