Что такое утечка памяти c
Я программист на C++ на платформе Windows. Я использую Visual Studio 2008.
Я обычно заканчиваю в коде с утечками памяти.
обычно я нахожу утечку памяти, проверяя код, но это громоздко и не всегда хороший подход.
поскольку я не могу позволить себе платный инструмент обнаружения утечки памяти, я хотел, чтобы вы, ребята, предложили наилучшие способы избежать утечки памяти.
- Я хочу знать, как программист можно найти утечки памяти.
- есть ли какой-либо стандарт или процедура, которой следует следовать, чтобы убедиться, что в программе нет утечки памяти?
- инструкции
вещи, которые вам понадобятся
- владение C++
- C++ компилятор
- отладчик и другие следственные программные средства
понимание основ оператора. Оператор C++ " new " выделяет память кучи. Оператор "delete" освобождает память кучи. Для каждого " нового "вы должны использовать" удалить", чтобы освободить ту же память, что и вы выделено:
перераспределить память, только если вы удалили. В приведенном ниже коде str получает новый адрес со вторым распределением. Первый адрес потерян безвозвратно, а также 30 байтов, на которые он указал. Теперь их невозможно освободить, и у вас есть утечка памяти:
следите за этими назначениями указателей. Каждая динамическая переменная (выделенная память в куче) должна быть связана с указателем. Когда динамическая переменная отсоединяется от указателя(указателей), ее невозможно стереть. Опять же, это приводит к утечке памяти:
будьте осторожны с местных указателей. Указатель, объявленный в функции, выделяется в стеке,но динамическая переменная, на которую он указывает, выделяется в куче. Если вы не удалите его, он будет сохраняться после выхода программы из функции:
обратите внимание на квадрат скобки после " удалить."Используйте" удалить " сам по себе, чтобы освободить один объект. Используйте "delete" [] с квадратными скобками, чтобы освободить массив кучи. Не делайте ничего подобного:
вы можете использовать некоторые методы в коде, чтобы обнаружить утечки памяти. Самый распространенный и самый простой способ обнаружения-определить макрос, скажем, DEBUG_NEW и использовать его вместе с предопределенными макросами, такими как __FILE__ и __LINE__ чтобы найти утечку памяти в коде. Эти предопределенные макросы сообщают вам номер файла и строки утечек памяти.
DEBUG_NEW - это просто макрос, который обычно определяется как:
так что везде, где вы используете new , Он также может отслеживать файл и номер строки, которые могут быть использованы для обнаружения утечки памяти в вашей программе.
и __FILE__ , __LINE__ are предопределенный макрос которые оценивают имя файла и номер строки соответственно, где вы их используете!
прочитайте следующую статью, которая объясняет технику использования DEBUG_NEW с другими интересными макросами, очень красиво:
Debug_new относится к технике в C++ перегрузка и / или переопределение оператора new и оператор delete, чтобы перехватить выделение памяти и вызовы освобождения и, таким образом, отладка a программа для использования памяти. часто включает в себя определение макроса с именем DEBUG_NEW, и делает новый стать что-то вроде нового(__,_строка_) для записи информации о файле / строке распределение. Microsoft Visual C++ использует этот метод в своей Microsoft Фундаментальные Классы. Есть некоторые способы расширения этого метода, чтобы избежать использование переопределения макросов возможность отображения файла / строки информация о некоторых платформах. Там многие недостатки, присущие этому метод. Это относится только к C++, и не удается поймать утечки памяти C функции, такие как malloc. Однако, он может очень прост в использовании, а также очень быстро, по сравнению с некоторыми другими полные решения отладчика памяти.
есть некоторые известные приемы программирования, которые помогут вам свести к минимуму риск утечки памяти "из первых рук":
- скачать инструменты отладки для Windows.
- использовать gflags утилита для включения трассировки стека пользовательского режима.
- использовать UMDH чтобы сделать несколько снимков памяти вашей программы. Сделайте снимок до выделения памяти и второй снимок после точки, в которой, по вашему мнению, произошла утечка памяти. Вы можете добавить паузы или подсказки в свою программу, чтобы дать вам возможность запустить UMDH и принять снимок.
- Run UMDH снова, на этот раз в своем режиме, что делает разницу между двумя снимками. Затем он создаст отчет, содержащий стеки вызовов подозрительных утечек памяти.
- восстановить предыдущие gflags настройки, когда вы закончите.
UMDH даст вам больше информации, чем куча отладки CRT, потому что она следит за распределением памяти по всему процессу; она даже может сказать вам, являются ли сторонние компоненты протечки.
Если вы используете gcc, доступен gprof.
Я хотел узнать, как программист находит утечку памяти
некоторые используют инструменты, некоторые делают то, что вы делаете, также могут через peer code review
есть ли какой-либо стандарт или процедура, которой следует следовать, чтобы убедиться, что в программе нет утечки памяти
для меня: всякий раз, когда я создаю динамически выделенные объекты, я всегда помещаю освобождающий код после, а затем заполняю код между. Это было бы нормально, если вы уверены, что в коде между ними не будет исключений. В противном случае я использую try-finally (я не часто использую C++).
в visual studio есть встроенный детектор утечки памяти, называемый библиотекой времени выполнения C. Когда ваша программа завершит работу после возвращения основной функции, CRT проверит отладочную кучу вашего приложения. если у вас есть какие-либо блоки, все еще выделенные в куче отладки, у вас есть утечка памяти..
этот форум обсуждает несколько способов избежать утечки памяти в C / C++..
поиск кода для вхождения new и убедитесь, что все они происходят в конструкторе с соответствующим удалением в деструкторе. Убедитесь, что это единственная возможная операция метания в этом конструкторе. Простой способ сделать это-обернуть все указатели в std::auto_ptr или boost::scoped_ptr (в зависимости от того, нужна ли вам семантика перемещения). Для всего будущего кода просто убедитесь, что каждый ресурс принадлежит объекту, который очищает ресурс в своем деструкторе. Если вам нужно двигаться семантика затем вы можете перейти на компилятор, который поддерживает ссылки на r-значения (VS2010 я считаю) и создавать конструкторы перемещения. Если вы не хотите этого делать, вы можете использовать различные хитрые методы, включающие добросовестное использование swap, или попробовать Boost.Библиотека.
запуск "Valgrind" может:
1) Помогите Определить Утечки Памяти - показать вам, сколько утечек памяти у вас есть, и указать на строки в коде, где была выделена утечка памяти.
2) укажите неправильные попытки освободить память (например, неправильный вызов "удалить")
инструкция по использованию "Valgrind"
1) Получить valgrind здесь.
1) составить ваш код с флагом-g
3) в вашей оболочке запустите:
где "myprog" - это ваша скомпилированная программа и" arg1"," arg2 " аргументы вашей программы.
4) результатом является список вызовов malloc / new, которые не имели последующих вызовов для бесплатного удаления.
например:
сообщает вам, в какой строке был вызван malloc (который не был освобожден).
Как указано другими, убедитесь, что для каждый вызов"new"/" malloc "у вас есть последующий вызов"delete"/" free".
в Windows вы можете использовать куча отладки CRT.
есть ли какой-либо стандарт или процедура, которой следует следовать, чтобы убедиться, что в программе нет утечки памяти.
Да, не используйте ручное управление памятью (если вы когда-нибудь позвоните delete или delete[] вручную, то вы делаете это неправильно). Используйте RAII и смарт-указатели, ограничьте выделение кучи до абсолютного минимума (в большинстве случаев достаточно автоматических переменных).
отвечая на вторую часть вашего вопроса,
есть ли какой-либо стандарт или процедура, которой следует следовать, чтобы убедиться, что в программе нет утечки памяти.
Да, есть. И это одно из ключевых различий между C и C++.
в C++, вы никогда не должны называть new или delete в коде пользователя. RAII - это очень распространенный метод, который в значительной степени решает проблему управления ресурсами. Каждый ресурс в вашей программе (ресурс-это все, что должно быть приобретено, а затем выпущено: дескрипторы файлов, сетевые сокеты, подключения к базе данных, а также простые выделения памяти, а в некоторых случаях пары вызовов API (BeginX () / EndX (), LockY (), UnlockY ()) должны быть обернуты в класс, где:
- конструктор приобретает ресурс (по вызову new Если ресурс является распределением memroy)
- деструктор выпускает ресурс
- копирование и назначение либо предотвращается (делая конструктор копирования и операторы назначения частными), либо реализованы для правильной работы (например, путем клонирования базового ресурса)
этот класс затем создается локально, в стеке или как член класса, и не по телефону new и сохранение указателя.
вам часто не нужно определять эти классы себе. Стандартные контейнеры библиотеки ведут себя таким же образом, так что любой объект, хранящийся в std::vector освобождается при уничтожении вектора. Поэтому снова не храните указатель в контейнере (что потребует вы называть new и delete ), а объект (который дает вам управление памятью бесплатно). Аналогично, классы интеллектуальных указателей можно использовать для простого обертывания объектов, которые просто должны быть выделены new , и контролировать свои жизни.
это означает, что когда объект выходит за рамки, он автоматически уничтожается, а его ресурс освобождается и очищается.
если вы будете делать это последовательно на протяжении всего кода, у вас просто не будет утечек памяти. Все, что мог бы get leaked привязан к деструктору, который гарантированно вызывается, когда элемент управления покидает область, в которой был объявлен объект.
визуальный детектор утечки (VLD) свободная, робастная, система обнаружения утечки памяти открыт-источника для Visual C++.
при запуске программы под отладчиком Visual Studio Visual Leak Detector выведет отчет об утечке памяти в конце сеанса отладки. Отчет об утечке включает полный стек вызовов показывает, как были выделены любые просочившиеся блоки памяти. Дважды щелкните строку в стеке вызовов, чтобы перейти к этому файлу и строке в окно редактора.
Если у вас есть только аварийные свалки, вы можете использовать Windbg !heap -l команда, она обнаружит просочившиеся блоки кучи. Лучше откройте опцию gflags: "создать базу данных трассировки стека пользовательского режима", тогда вы увидите стек вызовов выделения памяти.
MTuner свободный Multi профилировать памяти платформы, обнаружение утечки и инструмент анализа поддерживая компиляторы MSVC, GCC и Clang. Особенности включают в себя:
- хронология на основе истории использования памяти и блоков оперативной памяти
- сильная фильтрация деятельности памяти основанная на куче, бирке памяти, ряде времени, ЕТК.
- SDK для ручного инструментария с полным исходным кодом
- непрерывная поддержка интеграции через командную строку использование
- вызов дерева стека и дерева карты навигации
- гораздо больше.
пользователи могут профилировать любые программные платформы таргетинга с помощью GCC или Clang cross компиляторы. MTuner поставляется со встроенной поддержкой платформ Windows, PlayStation 4 и PlayStation 3.
AddressSanitizer (ASan) быстрый детектор ошибок памяти. Он находит ошибки переполнения буфера use-after-free и в программах на C/C++. Он находит:
- использовать после свободного (болтающийся указатель разыменования)
- переполнение буфера кучи
- переполнение буфера стека
- глобальное переполнение буфера
- использовать после возвращения
- порядок инициализации ошибок
этот инструмент очень быстро. Среднее замедление инструментальной программы составляет
вы можете использовать инструмент Valgrind для обнаружения утечек памяти.
кроме того, чтобы найти утечку в определенной функции, используйте exit(0) в конце функции, а затем запустите ее с Valgrind
в дополнение к инструментам и методам, предоставленным в других anwers, инструменты статического анализа кода могут использоваться для обнаружения утечек памяти (и других проблем). Бесплатный надежный инструмент-Cppcheck. Но есть много других доступных инструментов. Википедия список инструментов статического анализа кода.
Это может помочь тем, кто просто хочет использовать Visual Studio для обнаружения утечек. "Диагностические инструменты" поставляются с версиями VS 2015 и выше. Также пробовал инструмент под названием "Deleaker", но Visual studio tool одинаково хорош. Просмотр следующего видео помог мне начать его.
ни "новый", ни "удалить" никогда не должны использоваться в коде приложения. Вместо этого создайте новый тип, использующий идиому manager/worker, в котором класс manager выделяет и освобождает память и пересылает все остальные операции рабочему объекту.
к сожалению, это больше работы, чем должно быть, потому что C++ не имеет перегрузки оператора".". Это еще большая работа при наличии полиморфизма.
но это стоит усилий, потому что вы тогда не нужно беспокоиться об утечках памяти, а это значит, что вам даже не придется их искать.
Также приглашаем поучаствовать в открытом вебинаре на тему «Методы LINQ, которые сделают всё за вас» — на нем участники обсудят шесть представителей семейства технологий LINQ, три составляющих основной операции запроса, отложенное и немедленное выполнение, параллельные запросы.
Любой, кто работал на крупном корпоративном проекте, знает, что утечки памяти подобны крысам в большом отеле. Вы можете не замечать их, когда их мало, но вы всегда должны быть начеку на случай, если они расплодятся, проберутся на кухню и загадят все вокруг.
В среде со сборкой мусора термин «утечка памяти» представляется немного контринтуитивным. Как может произойти утечка памяти, когда есть сборщик мусора (GC — garbage collector), который берет на себя задачу высвобождения памяти?
На это есть две основные связанные между собой причины. Первая основная причина — это когда у вас есть объекты, на которые все еще ссылаются, но которые фактически не используются. Поскольку на них ссылаются, сборщик мусора не будет их удалять, и они останутся нетронутыми навсегда, занимая память. Это может произойти, например, когда вы подписываетесь на event и никогда не отменяете подписку.
Давайте же перейдем к моему списку лучших практик:
1. Обнаружение утечек памяти с помощью окна средств диагностики
Если вы перейдете в Debug | Windows | Show Diagnostic Tools, вы увидите это окно. Как и я когда-то, вы, вероятно, уже видели это окно после установки Visual Studio, сразу же закрыли его и никогда больше о нем не вспоминали. Окно средств диагностики может быть весьма полезным. Оно может помочь вам легко обнаружить 2 проблемы: утечки памяти и GC Pressure (давление на сборщик мусора).
Когда у вас есть утечки памяти, график использования памяти процессом (Process Memory) выглядит следующим образом:
По желтым линиям, идущим сверху, вы можете наблюдать, как сборщик мусора пытается высвободить память, но загруженность памяти все-равно продолжает расти.
В случае GC Pressure, график использования памяти процессом выглядит следующим образом:
GC Pressure — это когда вы создаете и удаляете новые объекты настолько быстро, что сборщик мусора просто не успевает за вами. Как вы видите на картинке, объем потребляемой памяти близок к своему пределу, а сборка мусора происходит очень часто.
С помощью этого метода вы не сможете найти определенные утечки памяти, но вы навскидку можете обнаружить, что у вас есть проблема с утечкой памяти, что само по себе уже несет пользу. В Visual Studio Enterprise окно средств диагностики также включает встроенный профилировщик памяти, который позволяет обнаружить конкретную утечку. Мы поговорим о профилировании памяти в третьем пункте.
2. Обнаружение утечек памяти с помощью диспетчера задач, Process Explorer или PerfMon
Второй самый простой способ обнаружить серьезные проблемы с утечками памяти — с помощью диспетчера задач (Task Manager) или Process Explorer (от SysInternals). Эти инструменты могут показать объем памяти, который использует ваш процесс. Если она постоянно увеличивается со временем, возможно, у вас утечка памяти.
PerfMon немного сложнее в использовании, но у него есть хороший график потребления памяти с течением времени. Вот график моего приложения, которое бесконечно выделяет память, не освобождая ее. Я использую счетчик Process | Private Bytes.
Обратите внимание, что этот метод заведомо ненадежен. Вы можете наблюдать увеличение потребления памяти только потому, что еще не отработал сборщик мусора. Также стоит вопрос об общей и приватной памяти, поэтому вы можете упустить утечки памяти и/или диагностировать утечки, которые не являются вашими собственными (объяснение). Наконец, вы можете принять утечку памяти за GC Pressure. В этом случае у вас нет утечек памяти, но вы создаете и удаляете объекты так быстро, что сборщик мусора не поспевает за вами.
Несмотря на недостатки, я упоминаю эту технику, потому что она проста в использовании и иногда является вашим единственным подручным инструментом. Это также хороший индикатор того, что что-то не так (при наблюдении в течение очень длительного периода времени).
3. Использование профилировщика памяти для обнаружения утечек
Профилировщик в работе с утечками памяти подобен ножу шеф-повара. Это основной инструмент для их поиска и исправления. Хотя другие методы могут быть проще в использовании или дешевле (лицензии на профилировщики стоят дорого), все таки стоит овладеть навыком работы хотя с бы один профилировщиком памяти, чтобы при необходимости эффективно решать проблемы утечек памяти.
Все профилировщики работают приблизительно одинаково. Вы можете подключиться к запущенному процессу или открыть файл дампа. Профилировщик создаст снапшот текущей памяти из кучи вашего процесса. Вы можете анализировать снапшот различными способами. Например, вот список всех аллоцированных объектов в текущем снапшоте:
Вы можете увидеть, сколько аллоцировано экземпляров каждого типа, сколько памяти они занимают и путь ссылки на GC Root.
Самый быстрый и полезный метод профилирования — это сравнение двух снапшотов, в которых память должна вернуться в одно и то же состояние. Первый снимок делается перед операцией, а второй после выполнения операции. Например, вы можете повторить эти шаги:
Начните с какого-либо состояния бездействия (Idle state) в вашем приложении. Это может быть Главное меню или что-то в этом роде.
Сделайте снапшот с помощью профилировщика памяти, присоединившись к процессу или сохранив дамп.
Запустите операцию, про которую вы подозреваете, что при ней возникла утечка памяти. Вернитесь в состояние бездействия по ее окончании.
Сделайте второй снапшот.
Сравните оба снапшота с помощью своего профилировщика.
Изучите New-Created-Instances, возможно, это утечки памяти. Изучите «path to GC Root» и попытайтесь понять, почему эти объекты не были освобождены.
Вот отличное видео, где в профилировщике памяти SciTech сравниваются два снапшота, в результате чего обнаруживается утечка памяти:
4. Используйте «Make Object ID» для поиска утечек памяти
Предположим, вы подозреваете, что в определенном классе есть утечка памяти. Другими словами, вы подозреваете, что после выполнения определенного сценария этот класс остается ссылочным и никогда не собирается сборщиком мусора. Чтобы узнать, действительно ли сборщик мусора собрал его, выполните следующие действия:
Поместите точку останова туда, где создается экземпляр класса.
Наведите курсор на переменную, чтобы открыть всплывающую подсказку отладчика, затем щелкните правой кнопкой мыши и используйте Make Object ID . Вы можете ввести в окне Immediate $1 , чтобы убедиться, что Object ID был создан правильно.
Завершите сценарий, который должен был освободить ваш экземпляр от ссылок.
Спровоцируйте сборку мусора с помощью известных волшебных строчек кода.
5. В появившемся окне непосредственной отладки введите $1 . Если оно возвращает null , значит, сборщик мусора собрал ваш объект. Если нет, у вас утечка памяти.
Здесь я отлаживаю сценарий с утечкой памяти:
А здесь я отлаживаю аналогичный сценарий, в котором нет утечек памяти:
Вы можете принудительно выполнить сборку мусора, вводя волшебные строки в окне непосредственной отладки, что делает эту технику полноценной отладкой без необходимости изменять код.
5. Избегайте известных способов заиметь утечки памяти
Риск нарваться на утечки памяти есть всегда, но есть определенные паттерны, которые помогут получить их с большей вероятностью. Я предлагаю быть особенно осторожным при их использовании и проактивно проверять утечки памяти с помощью таких методов, как последний приведенный здесь пункт.
Вот некоторые из наиболее распространенных подозреваемых:
Статические переменные, коллекции и, в частности, статические события всегда должны вызывать подозрения. Помните, что все статические переменные являются GC Roots, поэтому сборщик мусора никогда не собирает их.
Кэширование — любой тип механизма кэширования может легко вызвать утечку памяти. Кэширую информацию в памяти, в конечном итоге он переполнится и вызовет исключение OutOfMemory. Решением может быть периодическое удаление старых элементов или ограничение объема кэширования.
Привязки WPF могут быть опасными. Практическое правило — всегда выполнять привязку к DependencyObject или к INotifyPropertyChanged. Если вы этого не сделаете, WPF создаст сильную ссылку на ваш источник привязки (то есть ViewModel) из статической переменной, что приведет к утечке памяти. Дополнительную информацию о WPF утечках можно найти в этом полезном треде StackOverflow.
Захваченные члены. Может быть достаточно очевидно, что метод обработчика событий подразумевает, что на объект ссылаются, но когда переменная захвачена анонимным методом — на нее также ссылаются. Вот пример такой утечки памяти:
Потоки, которые никогда не завершаются. Live Stack каждого из ваших потоков считается GC Root. Это означает, что до тех пор, пока поток не завершится, любые ссылки из его переменных в стеке не будут собираться сборщиком мусора. Это также включает таймеры. Если обработчик тиков вашего таймера является методом, то объект метода считается ссылочным и не собирается. Вот пример такой утечки памяти:
6. Используйте шаблон Dispose для предотвращения утечек неуправляемой памяти
Оператор using за кулисами преобразует код в оператор try / finally , где метод Dispose вызывается в finally .
Когда вы сами выделяете неуправляемые ресурсы, вам определенно следует использовать шаблон Dispose . Вот пример:
Смысл этого шаблона — разрешить явное удаление ресурсов. А также чтобы добавить гарантии того, что ваши ресурсы будут удалены во время сборки мусора (в Finalizer ), если Dispose() не был вызван.
GC.SuppressFinalize(this) также имеет важное значение. Она гарантирует, что Finalizer не будет вызван при сборке мусора, если объект уже был удален. Объекты с Finalizer-ами освобождаются иначе и намного дороже. Finalizer добавляется к F-Reachable-Queue , которая позволяет объекту пережить дополнительную генерацию сборщика мусора. Есть и другие сложности.
7. Добавление телеметрии памяти из кода
Иногда вам может понадобиться периодически регистрировать использование памяти. Возможно, вы подозреваете, что на вашем рабочем сервере есть утечка памяти. Возможно, вы захотите предпринять какие-то действия, когда ваша память достигнет определенного предела. Или, может быть, у вас просто есть хорошая привычка следить за своей памятью.
Из самого приложения мы можем получить много информации. Получить текущую используемую память очень просто:
Для получения дополнительной информации вы можете использовать PerformanceCounter — класс, который используется для PerfMon :
Заключение
Не знаю, как у вас, но моя цель, поставленная на новый год, такова: лучшее управление памятью.
Я надеюсь, что эта статья принесет вам пользу и я буду рад, если вы подпишетесь на мой блог или оставите комментарий ниже. Любые отзывы приветствуются.
Утечки памяти представляют собой наиболее незаметные и сложные для обнаружения ошибки в приложениях C/C++. Утечки памяти появляются в результате неправильного освобождения выделенной памяти. Небольшая утечка памяти сначала может остаться незамеченной, но постепенно может приводить к различным симптомам: от снижения производительности до аварийного завершения приложения из-за нехватки памяти. Приложение, в котором происходит утечка памяти, может использовать всю доступную память и привести к аварийному завершению других приложений, в результате чего может быть непонятно, какое приложение отвечает за сбой. Даже безобидная утечка памяти может быть признаком других проблем, требующих устранения.
Отладчик Visual Studio и библиотека времени выполнения C (CRT) позволяют обнаруживать и выявлять утечки памяти.
Включение обнаружения утечек памяти
Основным средством для обнаружения утечек памяти является отладчик C/C++ и отладочные функции кучи библиотеки времени выполнения C (CRT).
Чтобы включить все отладочные функции кучи, вставьте в программу C++ следующие операторы в следующем порядке:
Включение файла crtdbg.h сопоставляет функции malloc и free с их отладочными версиями, _malloc_dbg и _free_dbg, которые отслеживают выделение и освобождение памяти. Это сопоставление используется только в отладочных построениях, в которых определен _DEBUG . В окончательных построениях используются первоначальные функции malloc и free .
После того как с помощью этих операторов будут включены отладочные функции кучи, можно поместить вызов _CrtDumpMemoryLeaks перед точкой выхода приложения для отображения отчета об утечке памяти перед завершением работы приложения.
Если приложение имеет несколько выходов, вам не нужно вручную размещать _CrtDumpMemoryLeaks в каждой точке выхода. Для автоматического вызова _CrtDumpMemoryLeaks в каждой точке выхода поместите вызов _CrtSetDbgFlag в начале приложения с помощью следующих битовых полей:
По умолчанию _CrtDumpMemoryLeaks выводит отчет об утечке памяти в область Отладка окна Вывод . Если используется библиотека, она может переустановить вывод в другое расположение.
_CrtSetReportMode можно использовать для перенаправления отчета в другое расположение или обратно в окно вывода, как показано ниже:
Интерпретация отчета об утечке памяти
Если приложение не определяет _CRTDBG_MAP_ALLOC , _CrtDumpMemoryLeaks отображает отчет об утечке памяти, аналогичный следующему:
Если приложение определяет _CRTDBG_MAP_ALLOC , отчет об утечке памяти выглядит следующим образом:
Во втором отчете отображается имя файла и номер строки, в которой впервые было произведено выделение утекающей памяти.
Независимо от того, определен ли _CRTDBG_MAP_ALLOC , в отчете об утечке памяти отображается следующее.
- Номер выделения памяти, в этом примере — 18 .
- Тип блока, в примере — normal .
- Расположение памяти в шестнадцатеричном формате, в этом примере — 0x00780E80 .
- Размер блока, в этом примере — 64 bytes .
- Первые 16 байт данных в блоке, в шестнадцатеричном формате.
Типы блоков памяти: обычные, клиентские или CRT. Обычный блок — это обыкновенная память, выделенная программой. Клиентский блок — особый тип блока памяти, используемой программами MFC для объектов, для которых требуется деструктор. Оператор new в MFC создает либо обычный, либо клиентский блок, в соответствии с создаваемым объектом.
Блок CRT — это блок памяти, выделенной библиотекой CRT для внутреннего использования. Библиотека CRT обрабатывает освобождение этих блоков, поэтому CRT-блоки не будут отображаться в отчете об утечке памяти, если нет серьезных проблем с библиотекой CRT.
Существуют два других типа блоков памяти, которые никогда не отображаются в отчетах об утечке памяти. Свободный блок — это блок памяти, которая была освобождена, поэтому по определению утечки здесь нет. Пропускаемый блок — это память, специально помеченная для исключения из отчета об утечке памяти.
Предыдущие способы выявляют утечки памяти для памяти, выделенной с помощью стандартной функции malloc библиотеки CRT. Однако если программа выделяет память с использованием оператора new C++, то в отчете об утечке памяти вы увидите только имя файла и номер строки, где operator new вызывает _malloc_dbg . Чтобы создать более полезный отчет об утечке памяти, можно написать макрос следующего вида, и в отчете будет указываться строка, в которой было выполнено выделение:
Теперь можно заменить оператор new с помощью макроса DBG_NEW в коде. В отладочных сборках DBG_NEW использует перегрузку глобальных operator new , которая принимает дополнительные параметры для типа блока, файла и номера строки. Перегрузка new вызывает _malloc_dbg для записи дополнительных сведений. Отчеты об утечке памяти показывают имя файла и номер строки, в которой были выделены утечки объектов. Сборки выпуска по-прежнему используют new по умолчанию. Вот пример этого метода:
При выполнении этого кода в отладчике Visual Studio вызов _CrtDumpMemoryLeaks создает отчет в окне вывода, который выглядит аналогичным образом:
Эти выходные данные сообщают о том, что утечка памяти находилась на строке 20 файла debug_new.cpp.
Не рекомендуется создавать макрос препроцессора с именем new или любым другим ключевым словом языка.
Задание точек останова для номера выделения памяти
Номер выделения можно использовать для того, чтобы задать точку останова в том месте, где выделяется память.
Установка точки останова для выделения памяти с помощью окна контрольных значений:
Установите точку останова рядом с началом приложения и запустите отладку.
Когда приложение приостанавливается в точке останова, откройте окно Контрольные значения, последовательно выбрав пункты Отладка > Windows > Контрольные значения 1 (или Контрольные значения 2, Контрольные значения 3 или Контрольные значения 4).
В окне Контрольные значения введите _crtBreakAlloc в столбце Имя.
Если используется многопоточная версия DLL-библиотеки CRT (параметр /MD), добавьте контекстный оператор: _crtBreakAlloc
Убедитесь, что отладочные символы загружены. В противном случае _crtBreakAlloc будет отображаться как неидентифицированный.
Нажмите клавишу ВВОД.
Отладчик выполнит оценку вызова и поместит результат в столбец Значение . Это значение будет равно –1, если в местах выделения памяти не задано ни одной точки останова.
В столбце Значение замените отображаемое значение номером выделения памяти, на котором нужно приостановить выполнение.
После задания точки останова для номера выделения памяти можно продолжить отладку. Убедитесь, что соблюдаются те же условия, чтобы номер выделения памяти не изменился. Когда выполнение программы будет приостановлено на заданном выделении памяти, с помощью окна Стек вызовов и других окон отладчика определите условия выделения памяти. Затем можно продолжить выполнение программы и проследить, что происходит с этим объектом и почему выделенная ему память освобождается неправильно.
Иногда может быть полезно задать точку останова по данным на самом объекте. Для получения дополнительной информации см. раздел Использование точек останова.
Точки останова для выделения памяти можно также задать в коде. Можно установить следующие значения:
Сравнение состояний памяти
Другая технология для обнаружения утечек памяти включает получение "снимков" состояния памяти приложения в ключевых точках. Чтобы получить снимок состояния памяти в заданной точке приложения, создайте структуру _CrtMemState и передайте ее функции _CrtMemCheckpoint .
Функция _CrtMemCheckpoint поместит в структуру снимок текущего состояния памяти.
Чтобы вывести содержимое структуры _CrtMemState , передайте ее функции _ CrtMemDumpStatistics :
Функция _ CrtMemDumpStatistics выводит дамп состояния памяти, который выглядит примерно таким образом:
Чтобы определить, произошла ли утечка памяти на отрезке кода, можно сделать снимок состояния памяти перед ним и после него, а затем сравнить оба состояния с помощью функции _ CrtMemDifference :
Функция _CrtMemDifference сравнивает состояния памяти s1 и s2 и возвращает результат в ( s3 ), представляющий собой разницу между s1 и s2 .
Еще один способ поиска утечек памяти заключается в размещении вызовов _CrtMemCheckpoint в начале и конце программы с последующим использованием _CrtMemDifference для сравнения результатов. Если _CrtMemDifference показывает утечку памяти, можно добавить дополнительные вызовы функции _CrtMemCheckpoint , чтобы разделить программу с помощью двоичного поиска, пока не будет найден источник утечки.
Ложные срабатывания
Утечка памяти достаточно серьезная проблема возникающая при работе программы. Масштабность проявляется особенно при длительной работе программы, когда программа может исчерпать лимит выделения для нее памяти, а это приведет к очень нехорошим последствиям.
Простейшая программа с утечкой памяти:
В данном участке кода утечка памяти происходит из-за отсутствия оператора delete[] для массива p.
В этой статье рассмотрены:
1 Обнаружение утечек с библиотекой CRT
Библиотека CRT доступна в операционной системе Windows, она у вас уже есть если вы используете Microsoft Visual Studio. Для подключения анализатора памяти достаточно добавить пару макросов и собрать проект в отладочном режиме:
Видно, что первая группа макросов добавляется в самое начало вашей программы. За счет этих макросов все обращения в функциям new, malloc, free и delete заменяются на другие версии, которые помимо выделения/освобождения памяти сохраняют дополнительную информацию (сколько и где было выделено). Макрос _CrtDumpMemoryLeaks добавляется в конец программы (перед завершением работы) и выводит информацию о текущем состоянии памяти.
Для приведенной выше программы в окно отладки будет выведено следующее:
Утечка была обнаружена, кроме того, нам показывают ее размер и даже строку кода где это случилось.
2 Использование Visual Leak Detector
Все говото, утечки ищутся и выводятся в консоль отладки:
Видно, что VLD выводит информацию о стеке вызовов, но формат вывода крайне неудобный. Узнать в какой строке произошла утечка памяти совсем непросто.
3 Работа с Valgrind memcheck
Valgrind предоставляет множество инструментов для динамического анализа, поиском утечек памяти это не ограничивается. Работой с памятью занимается модуль memcheck. Для его использования необходимо:
1) Сстановить valgrind:
sudo apt install valgrind
2) Скомпилировать программу в debug-режиме и запустить ее через valgrind:
valgrind ./my_program
При таком запуске будет выведено сколько памяти было потеряно. Чтобы увидеть, где была выделена память, необходимо добавить опцию
--leak-check=full .
Результаты работы memcheck для нашего примера:
4 Как это устроено внутри
Подключение CRT приводит к оборачиванию функций работы с памятью в что-то такое:
Да и зачем этим заниматься если его готовый CRT? В свою очередь, Visual Leak Detector представляет собой обертку над CRT.
4 Более сложный пример
Возможно, вам кажется что проблем с поиском утечек не возникает, тогда посмотрите такой пример:
Cat() ведь деструктор
Результаты применения всех трех рассмотренных инструментов примерно одинаковы (разве что VLD не выводит информацию о строке возникновения ошибки). Ниже приведен вывод valgring memcheck:
Первая и наиболее важная задача – подтвердить наличие утечки памяти. Многие разработчики используют диспетчер задач windows для подтверждения наличия утечки памяти в приложении. Использование диспетчера задач не только вводит в заблуждение, но и не дает достаточной информации о том, где находится утечка памяти.
Сначала попытаемся понять, почему информация диспетчера задач о памяти вводит в заблуждение. Диспетчер задач показывает рабочий набор памяти, а не фактическую используемую память. Но что это означает? Эта память - выделенная память, а не используемая память. Добавление некоторого количества дополнительной памяти из рабочего набора может совместно использоваться другими процессами / приложением.
Рабочий набор памяти может быть больше по количеству, чем фактическая используемая память.
Чтобы получить верное количество памяти, используемой приложением, нужно отслеживать индивидуальные байты, используемые приложением. Индивидуальные байты – это те участки памяти, которые не используются совместно с другим приложением. Чтобы измерить индивидуальные байты, используемые приложением, нужно использовать счетчики производительности.
Ниже перечислены шаги, которым нужно следовать, чтобы отслеживать индивидуальные байты в приложении при помощи счетчиков производительности:-
- Запустите приложение, имеющее утечку памяти, и поддерживайте его в рабочем состоянии.
- Щелкните мышкой по запуску Goto (идти к) и наберите на клавиатуре ‘perfmon’.
- Удалите все имеющиеся счетчики производительности, выбирая счетчик и удаляя его путем нажатия на кнопку удаления.
- Нажмите правую кнопку мыши, выберите ‘Add counters (добавить счетчики)’ , выберите ‘process (процесс)’ из объекта производительности.
- Из списка счетчиков выберите ‘Private bytes (индивидуальные байты)’.
- Из списка копий выберите приложение, которое вы хотите проверить на утечку памяти.
Если приложение демонстрирует постоянный рост числа индивидуальных байтов, это значит, что в нем есть утечка памяти. На рисунке ниже можно увидеть, как количество индивидуальных байтов постоянно увеличивается, что подтверждает наличие утечки памяти в этом приложении.
Вышеприведенный график показывает линейное увеличение, но в реальной реализации могут пройти часы, прежде чем проявится тенденция роста. Чтобы проверить утечку памяти, счетчик производительности должен работать на рабочем сервере в течение нескольких часов или даже дней, чтобы проверить, есть ли на самом деле утечка памяти.
После подтверждения наличия утечки памяти нужно изучить основную проблему утечки памяти. Путь к решению делится на 3 этапа, отвечающих на вопросы: какой? как? и где?
- Какой? - Сначала нужно попытаться выяснить, каков тип утечки памяти – это управляемая утечка памяти или неуправляемая утечка памяти.
- Как? - Что в действительности вызывает утечку памяти? Это объект соединения, файл какого-то типа, дескриптор которого не закрыт, и т.д.?
- Где? - Которая функция / процедура или логика вызывает утечку памяти?
Сначала нужно удостовериться, каков тип утечки памяти – это управляемая утечка или неуправляемая утечка? Чтобы выяснить, управляемая это утечка или неуправляемая, нужно измерить два счетчика производительности.
Первый – счетчик индивидуальных байтов для приложения, который уже упоминался в предыдущем разделе.
Индивидуальные байты – это полная память, используемая приложением. Байты во всех кучах – это память, используемая управляемым кодом. Уравнение становится таким, как показано на рисунке ниже.
Неуправляемая память + Байты во всех кучах = индивидуальные байты, поэтому, если нужно найти неуправляемую память, можно вычесть байты во всех кучах из индивидуальных байтов.
Ниже даны два утверждения:
- Если индивидуальные байты увеличиваются, а байты во всех кучах остаются неизменными, это свидетельствует о неуправляемой утечке памяти.
- Если байты во всех кучах линейно увеличиваются, это свидетельствует об управляемой утечке памяти.
Ниже показан типовой скриншот неуправляемой утечки. Видно, что индивидуальные байты увеличиваются, в то время как байты в кучах остаются неизменными.
Ниже показан типовой скриншот управляемой утечки. Байты во всех кучах увеличиваются.
После выяснения типа утечки памяти нужно разобраться, как происходит утечка памяти. Иными словами, что вызывает утечку памяти.
Введем утечку неуправляемой памяти, вызвав функцию ‘Marshal.AllocHGlobal’. Эта функция выделяет неуправляемую память и таким образом вводит утечку неуправляемой памяти в приложение. Эта команда выполняется в пределах таймера такое количество раз, чтобы вызвать огромную неуправляемую утечку.
Управляемую утечку ввести очень трудно, так как GC обеспечивает восстановление памяти. Для упрощения утечка управляемой памяти моделируется путем создания множества объектов кисти и добавления их в список, являющийся переменной уровня класса. Это имитация, а не управляемая утечка. После закрытия приложения эта память будет восстановлена.
Запустите инструмент для диагностики при отладке и выберите ‘Утечка памяти и дескриптора’ нажмите следующее.
Выберите процесс, в котором вы хотите обнаружить утечку памяти.
В конце выберите ‘Активировать правило сейчас’.
Теперь оставьте приложение в работающем состоянии, и инструмент ‘Debugdiag’ будет выполняться во внутренней части, отслеживая выделение памяти.
После завершения нажмите на ‘начать анализ’ и позвольте инструменту выполнить анализ.
Вы должны получить подробный HTML отчет, показывающий, как была выделена неуправляемая память. В нашем коде была выделена огромная неуправляемая память при помощи ‘AllochGlobal’, что показано в отчете ниже.
mscorlib.ni.dll отвечает за выделение 3.59 Мбайт невыполненной памяти. Следующие 2 функции потребили больше всего памяти:
ntdll.dll отвечает за выделение 270.95 мегабайт невыполненной памяти. Следующие 2 функции потребили больше всего памяти:
ntdll!RtlpDphNormalHeapAllocate+1d: выделено 263.78 килобайт невыполненной памяти.
ntdll!RtlCreateHeap+5fc: выделено 6.00 килобайт невыполненной памяти.
Утечка управляемой памяти кистей показана при помощи ‘GdiPlus.dll’ в следующем HTML отчете.
GdiPlus.dll отвечает за выделение 399.54 килобайт невыполненной памяти.
Следующие 2 функции потребили больше всего памяти:
GdiPlus!GpMalloc+16: выделено 399.54 килобайт невыполненной памяти.
Как только вы узнали, что есть источник утечки памяти, нужно выяснить, какая логика вызывает утечку памяти. Нет автоматического инструмента для выявления логики, вызывающей утечки памяти. Вам нужно вручную войти в код и использовать указатели, предоставленные ‘debugdiag’, чтобы сделать вывод, в каких местах есть утечки памяти.
Например, из отчета ясно, что ‘AllocHGlobal’ вызывает неуправляемую утечку, в то время как один из объектов GDI вызывает управляемую утечку. Используя эти детали, нужно войти в код, чтобы выяснить, где именно находится утечка.
Вы можете загрузить из верхней части этой статьи исходный код, который может помочь вам вывести утечку памяти.
При копировании материалов наличие активной индексируемой ссылки на сайт обязательно.
Читайте также: