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


Обнаружение утечек памяти с помощью библиотеки CRT

Утечки памяти, определяемые как сбой при освобождении ранее выделенной памяти, — это одна из наиболее трудно обнаруживаемых ошибок в приложениях C/C++. Небольшая утечка памяти сначала может остаться незамеченной, но постепенно нарастающая утечка памяти может приводить к различным симптомам, от снижения производительности до аварийного завершения приложения из-за нехватки памяти. Более того, приложение, в котором происходит утечка памяти, может использовать всю доступную память и привести к аварийному завершению другого приложения, в результате чего может быть непонятно, какое приложение отвечает за сбой. Даже безобидная на первый взгляд утечка памяти может быть признаком других проблем, требующих устранения.

Отладчик Visual Studio и библиотеки времени выполнения C (CRT) предоставляют средства для обнаружения утечек памяти.

Включение обнаружения утечек памяти

Основным средством для обнаружения утечек памяти является отладчик и отладочные функции кучи библиотеки времени выполнения C (CRT).

Чтобы включить отладочные функции кучи, вставьте в программу следующие операторы:

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

Для правильной работы функций CRT операторы #include должны следовать в приведенном здесь порядке.

Включение заголовочного файла crtdbg.h сопоставляет функции malloc и free с их отладочными версиями, _malloc_dbg и free, которые отслеживают выделение и освобождение памяти. Это сопоставление используется только в отладочных построениях, в которых определен _DEBUG. В окончательных построениях используются первоначальные функции malloc и free.

Оператор #define сопоставляет базовые версии функций кучи CRT соответствующим отладочным версиям. Если оператор #define не используется, дамп утечки памяти будет менее подробным.

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

_CrtDumpMemoryLeaks();

Если у приложения несколько точек выхода, не требуется вручную размещать вызовы функции _CrtDumpMemoryLeaks в каждой точке выхода. Вызов функции _CrtSetDbgFlag в начале приложения приведет к автоматическому вызову функции _CrtDumpMemoryLeaks в каждой точке выхода. Необходимо установить два показанных здесь битовых поля:

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

По умолчанию _CrtDumpMemoryLeaks выводит отчет об утечке памяти в область Отладка окна Вывод. _CrtSetReportMode можно использовать для перенаправления отчета в другое расположение.

Если используется библиотека, она может переустановить вывод в другое расположение. В этом случае можно вернуть вывод обратно в окно Вывод, как показано ниже:

_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );

Интерпретация отчета об утечке памяти

Если приложение не определяет _CRTDBG_MAP_ALLOC, _CrtDumpMemoryLeaks отображает отчет об утечке памяти, выглядящий следующим образом:

Detected memory leaks!
Dumping objects ->
{18} normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

Если приложение определяет _CRTDBG_MAP_ALLOC, отчет об утечке памяти выглядит следующим образом:

Detected memory leaks!
Dumping objects ->
C:\PROGRAM FILES\VISUAL STUDIO\MyProjects\leaktest\leaktest.cpp(20) : {18} 
normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

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

Независимо от определения _CRTDBG_MAP_ALLOC, в отчете об утечке памяти отображаются следующие сведения:

  • Номер выделения памяти, в этом примере — 18.

  • Тип блока, в этом примере — normal.

  • Расположение памяти в шестнадцатеричном формате, в этом примере — 0x00780E80.

  • Размер блока, в этом примере — 64 bytes.

  • Первые 16 байт данных в блоке, в шестнадцатеричном формате.

В отчете об утечке памяти блок памяти может определяться как обычный, клиентский или CRT. Обычный блок — это обыкновенная память, выделенная программой. Клиентский блок — особый тип блока памяти, используемой программами MFC для объектов, для которых требуется деструктор. Оператор new в MFC создает либо обычный, либо клиентский блок, в соответствии с создаваемым объектом. Блок CRT — это блок памяти, выделенной библиотекой CRT для внутреннего использования. Освобождение этих блоков производится библиотекой CRT. Поэтому маловероятно увидеть их в отчете об утечке памяти — разумеется, если не возникнет серьезный сбой, например повреждение библиотеки CRT.

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

Эти способы работают для памяти, выделенной с помощью стандартной функции malloc библиотеки CRT. Однако если программа выделяет память с использованием оператора new C++, необходимо переопределить оператор new, если требуется, чтобы в отчете об утечке памяти отображались имя файла и номера строк. Это можно сделать с помощью блока кода, аналогичного приведенному ниже:

#ifdef _DEBUG
   #ifndef DBG_NEW
      #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
      #define new DBG_NEW
   #endif
#endif  // _DEBUG

Задание точек останова для номера выделения памяти

Номер выделения памяти сообщает, когда был выделен утекающий блок памяти. Например, блок с номером выделения памяти 18 — это 18-й блок памяти, выделенный во время выполнения программы. В отчете CRT учитываются все выделения блоков памяти во время выполнения. Сюда входят выделения, произведенные библиотекой CRT и другими библиотеками, такими как MFC. Поэтому блок с номером выделения памяти 18 может не быть 18-м блоком памяти, выделенным вашим кодов. Как правило, не будет.

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

Установка точки останова для выделения памяти с помощью окна контрольных значений

  1. Задайте точку останова недалеко от начала приложения, затем запустите приложение.

  2. Когда выполнение приложения остановится в точке останова, откройте окно Контрольные значения.

  3. В окне Контрольные значения введите _crtBreakAlloc в столбце Имя.

    Если используется многопоточная версия DLL библиотеки CRT (параметр /MD), добавьте контекстный оператор: {,,msvcr100d.dll}_crtBreakAlloc.

  4. Нажмите клавишу ВВОД.

    Отладчик выполнит оценку вызова и поместит результат в столбец Значение. Это значение будет равно -1, если в местах выделения памяти не задано ни одной точки останова.

  5. В столбце Значение замените отображаемое значение номером выделения памяти, на котором нужно приостановить выполнение.

После задания точки останова для номера выделения памяти можно продолжить отладку. Проследите за тем, чтобы программа была запущена в таких же условиях, как и в предыдущий раз, чтобы порядок выделения памяти не изменился. Когда выполнение программы будет приостановлено на заданном выделении памяти, с помощью окна Стек вызовов и других окон отладчика определите условия выделения памяти. Затем можно продолжить выполнение программы и проследить, что происходит с этим объектом и почему выделенная ему память освобождается неправильно.

Иногда может быть полезно задать точку останова по данным на самом объекте. Для получения дополнительной информации см. Set a data change breakpoint (native C++ only).

Точки останова для выделения памяти можно также задать в коде. Это можно сделать двумя способами.

_crtBreakAlloc = 18;

или

_CrtSetBreakAlloc(18);

Сравнение состояний памяти

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

_CrtMemState s1;
_CrtMemCheckpoint( &s1 );

Функция _CrtMemCheckpoint поместит в структуру снимок текущего состояния памяти.

Чтобы вывести содержимое структуры _CrtMemState, передайте ее функции _ CrtMemDumpStatistics:

_CrtMemDumpStatistics( &s1 );

Функция _ CrtMemDumpStatistics выводит дамп состояния памяти, который выглядит примерно таким образом:

0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
3071 bytes in 16 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 3071 bytes.
Total allocations: 3764 bytes.

Чтобы определить, произошла ли утечка памяти на отрезке кода, можно сделать снимок состояния памяти перед ним и после него, а затем сравнить оба состояния с помощью функции _ CrtMemDifference:

_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );

if ( _CrtMemDifference( &s3, &s1, &s2) )
   _CrtMemDumpStatistics( &s3 );

Функция _CrtMemDifference сравнивает состояния памяти s1 и s2 и возвращает результат в (s3), представляющий собой разницу s1 и s2.

Еще один способ поиска утечек памяти заключается в размещении вызовов _CrtMemCheckpoint в начале и конце программы с последующим использованием _CrtMemDifference для сравнения результатов. Если _CrtMemDifference показывает утечку памяти, можно добавить дополнительные вызовы функции _CrtMemCheckpoint, чтобы разделить программу с помощью двоичного поиска, пока не будет найден источник утечки.

Ложные срабатывания

В некоторых случаях _CrtDumpMemoryLeaks может ошибочно диагностировать утечку памяти. Это может произойти в случае использования библиотеки, в которой внутренние выделения отмечены как _NORMAL_BLOCK вместо _CRT_BLOCK или _CLIENT_BLOCK. В таком случае функция _CrtDumpMemoryLeaks не может различать пользовательские выделения и внутренние выделения библиотеки. Если глобальные деструкторы для выделений библиотеки выполняются после точки вызова функции _CrtDumpMemoryLeaks, каждое внутреннее выделение библиотеки принимается за утечку памяти. Предыдущие версии библиотеки стандартных шаблонов, предшествовавшие Visual Studio .NET, приводили к тому, что функция _CrtDumpMemoryLeaks сообщала о таких ложных утечках, но в последних выпусках это было исправлено.

См. также

Основные понятия

Безопасность отладчика

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

Сведения о куче отладки CRT

Отладка машинного кода