Программерские типсы и триксы: Ловим memory leaksВ «Диспетчере задач» очень часто можно увидеть, что некоторые программы занимают совершенно неприличное количество памяти. Особенно это свойственно интернет-браузерам. Рано или поздно перед каждым разработчиком встает задача отлова утечек памяти. Сегодня мы узнаем, как это сделать в языке C++ на MSVC. Искать утечки памяти мы будем в ОС Windows, а для сборки кода использовать компилятор от Microsoft. Существует множество способов избежать мемори ликов, но основное правило этой борьбы можно сформулировать как «класть на место все, что взяли». К сожалению, в «боевом кодинге» такое не всегда возможно. Банальный человеческий фактор или сработавший exception запросто может отменить выполнение оператора delete. В этой статье мы не будем рассматривать, что нужно делать, а что не стоит, чтобы память не утекала. Мы будем действовать в контексте уже имеющейся проблемы: утечка есть и нам надо ее перекрыть. Debug CRTДля использования Debug CRT надо подключить соответствующий хидер и включить использование Debug Heap Alloc Map. Делается это всего несколькими строками кода: Подключение Debug CRT
После этих действий при выделении памяти через new и malloc() данные будут оборачиваться в специальную структуру _CrtMemBlockHeader. С помощью этой обертки мы сможем узнать имя файла и строку, в которой резервировалась ликнутая память, ее объем и сами данные. Все записи объединены в двусвязный список, поэтому по нему можно легко пробежаться и найти проблемные участки. Структура _CrtMemBlockHeader
Чтобы пройтись по этому списку, нужно воспользоваться функцией _CrtDumpMemoryLeaks(). Она не принимает никаких параметров, а просто выводит список утекших блоков памяти. Но, к сожалению, она ничего не говорит нам о файле и строке, в которых выделялась память. Результат работы этой функции выглядит примерно так: Вывод _CrtDumpMemoryLeaks()
Вот оно как: в Microsoft Visual C++ 6.0 в файле crtdbg.h имело место переопределение функции new, которое должно было точно показать файл и строку, в котором происходило выделение памяти. Но оно не давало желаемого результата — __FILE__:__LINE__ всегда разворачивались в crtdbg.h file line 512. В следующих версиях Microsoft вообще убрали эту фичу, и весь груз ответственности лег на программистов. Сделать нормальный вывод можно с помощью следующего переопределения: Переопределение new
Эту строчку желательно вынести в какой-нибудь общий заголовочный файл и подключать его после crtdbg.h. Теперь перед нами стоит задача записать все это в какой-нибудь лог или хотя бы выводить в консоль. Для перенаправления вывода нам потребуются две функции: _CrtSetReportMode и _CrtSetReportFile. Вторым параметром _CrtSetReportFile может быть хендл нашего лог-файла или флаг вывода в stdout. Перенаправление вывода
У этого метода есть еще одна проблемка — он выводит информацию о памяти, которая не утекла, а просто не успела вернуться. Это, например, может быть какая-нибудь глобальная переменная или объект. Нам нужно как-то удалить эти куски памяти из вывода _CrtDumpMemoryLeaks(). Делается это следующим образом: Ограничение зоны действия _CrtDumpMemoryLeaks()
Мы записываем начальное состояние памяти в специальную структуру с помощью функции _CrtMemCheckpoint(), а в конце, используя _CrtMemDumpAllObjectsSince(), выводим все, что утекло после того, как мы сделали слепок памяти. Вот так вот, с помощью нехитрых функций Debug CRT, мы можем достаточно эффективно бороться с мемори ликами в нашей программе. Конечно, это не заменит серьезных библиотек по отлову утечек, но вполне подойдет для небольших проектов. Visual Leak DetectorVisual Leak Detector — это уже сторонняя библиотека, но по сути она является надстройкой над Debug CRT, которую мы рассмотрели чуть раньше. Пользоваться ей достаточно просто — надо всего лишь включить заголовочный файл vld.h в любой файл проекта. Но с двумя оговорками. Во-первых, если у нас в проекте есть несколько бинарных модулей (DLL или EXE), то include для vld.h надо делать как минимум в одном исходном файле для каждого модуля. То есть, если у нас после компиляции на выходе получается module_1.dll и module_2.dll, то нам нужно сделать #include <vld.h> как минимум в module_1.h и в module_2.h. Во-вторых, включение заголовочного файла Visual Leak Detector должно происходить после включения прекомпилед хидера. То есть, после stdafx.h и других подобных файлов. После выполнения этих условий достаточно запустить программу в дебаг-сборке, и библиотека сразу начнет работать. Visual Leak Detector можно настроить под свои нужды. Конфиг хранится в файле vld.ini, который, в свою очередь, лежит в директории с установленным VLD. Файл с настройками можно скопировать в папку с проектом, и тогда библиотека будет использовать эту копию с индивидуальными настройками для каждого проекта. Если мы включим вывод в файл, то надо указать путь к этому файлу с помощью параметра ReportFile. Также можно выбрать кодировку файла с помощью ReportEncoding: unicode или ASCII. Еще есть интересная опция поиска ликов библиотеки в самой себе (SelfTest). Да, бывает и такое. Если постараться, то можно получить в output что-то вроде этого:
Еще VLD позволяет ограничить размер дампа памяти, выводимого в лог, или вообще подавить этот вывод, настроить глубину и метод проходки по стеку и так далее. Но это уже специфичные вещи, которые некоторым могут пригодиться, а некоторым и нет. В общем и целом Visual Leak Detector прост, удобен и не требует много кода для включения режима поиска утечек. ValgrindВсе, что мы рассмотрели выше, было актуально для Windows и MS Visual Studio. Но есть и другие ОС. Valgrind как раз для них. Он работает в Linux и Mac OS X и используется не только для отлова мемори ликов, но и для отладки памяти и профилирования (сбор характеристик работы программы). По сути, Valgrind является виртуальной машиной, использующей методы JIT-компиляции. Ее усилиями программа не выполняется непосредственно на процессоре компьютера, а транслируется в так называемое «промежуточное представление». С этим представлением и работает Valgrind. Точнее, работают его инструменты, но об этом чуть позже. После обработки промежуточного представления Valgrind перегоняет все обратно в машинный код. Такие действия значительно (в 4-5 раз) замедляют выполнение программы. Но это вполне обоснованная плата за контроль расхода памяти. Как я говорил выше, Valgrind предоставляет несколько инструментов. Самый популярный из них — Memcheck. Он заменяет стандартное выделение памяти языка C собственной реализацией. Memcheck обнаруживает попытки использования неинициализированной памяти, чтение/запись после её освобождения и с конца выделенного блока, а также утечки памяти. Есть и другие инструменты, например Addrcheck — более легкая, но менее функциональная версия Memcheck. Инструменты Helgrind и DRD используются для поиска ошибок в многопоточном коде. Иначе говоря, Valgrind гораздо более мощная штука, чем просто библиотека по поиску мемори ликов. ЗаключениеУтечки памяти — одна из самых распространенных проблем в программировании. Даже самые опытные из нас могут заиметь парочку ликов. Инструментов для их отлова великое множество, и эти несколько страниц должны помочь тебе начать борьбу с memory leaks. |