Обнаружение утечек памяти с помощью библиотеки 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-м блоком памяти, выделенным вашим кодов.Как правило, не будет.
Номер выделения можно использовать для того, чтобы задать точку останова в том месте, где выделяется память.
Установка точки останова для выделения памяти с помощью окна контрольных значений
Задайте точку останова недалеко от начала приложения, затем запустите приложение.
Когда выполнение приложения остановится в точке останова, откройте окно Контрольные значения.
В окне Контрольные значения введите _crtBreakAlloc в столбце Имя.
Если используется многопоточная версия DLL библиотеки CRT (параметр /MD), добавьте контекстный оператор: {,,msvcr100d.dll}_crtBreakAlloc
Нажмите клавишу ВВОД.
Отладчик выполнит оценку вызова и поместит результат в столбец Значение.Это значение будет равно -1, если в местах выделения памяти не задано ни одной точки останова.
В столбце Значение замените отображаемое значение номером выделения памяти, на котором нужно приостановить выполнение.
После задания точки останова для номера выделения памяти можно продолжить отладку.Проследите за тем, чтобы программа была запущена в таких же условиях, как и в предыдущий раз, чтобы порядок выделения памяти не изменился.Когда выполнение программы будет приостановлено на заданном выделении памяти, с помощью окна Стек вызовов и других окон отладчика определите условия выделения памяти.Затем можно продолжить выполнение программы и проследить, что происходит с этим объектом и почему выделенная ему память освобождается неправильно.
Иногда может быть полезно задать точку останова по данным на самом объекте.Дополнительные сведения см. в разделе Практическое руководство. Установка точки останова для данных (только машинный код).
Точки останова для выделения памяти можно также задать в коде.Это можно сделать двумя способами.
_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 сообщала о таких ложных утечках, но в последних выпусках это было исправлено.