Как найти утечку памяти c qt
Обновлено: 04.07.2024
1. Введение
Механизм управления памятью Qt: Qt может поддерживать иерархию объектов внутри. Для визуальных элементов эта иерархия представляет собой отношение между дочерними компонентами и родительскими компонентами, для невизуальных элементов - это отношение подчиненности между одним объектом и другим объектом. В Qt, в Qt, удаление родительского объекта удаляет его дочерние объекты вместе.
Удаление и новое в C ++ должно быть сопряжено и использовано (однозначное соответствие): если будет меньше удалений, то утечек памяти и больше проблем будет больше. Qt использует new, но редко удаляет, потому что класс QObject и его унаследованный класс устанавливают родителя (вы также можете использовать функцию setParent или родительский addChild во время конструирования), поэтому при удалении родителя все дочерние элементы, связанные с этим родителем, будут Автоматическое удаление без обработки вручную. Но parent не различает, является ли его дочерний элемент новым или размещен в стеке. Это отражает силу удаления, которое может освободить любые объекты, и объекты в стеке удаления вызовут ошибки памяти. Это требует понимания полуавтоматического управления памятью в Qt. Еще одна проблема: ребенок не знает, удаляется ли он сам, поэтому могут появляться дикие указатели. Необходимо понимать умный указатель Qt QPointer.
2. Подробное объяснение
1. Полуавтоматическое управление памятью в Qt
(1) Для объектов QObject и его производных классов, если его родитель не равен 0, объект будет уничтожен при разрушении его родителя.
(2) Для QWidget и объектов его производного класса вы можете установить флаг Qt :: WA_DeleteOnClose (объект будет разрушен при закрытии).
(3) Объекты, полученные из QAbstractAnimation, могут быть установлены в QAbstractAnimation :: DeleteWhenStopped.
(5) Родительско-дочерние отношения: родительский объект, дочерний объект, родительско-дочерние отношения. Это уникально для Qt и не имеет ничего общего с отношением наследования класса. Передаваемые параметры относятся к родительскому (базовый класс, производный класс или родительский класс, дочерний класс, это для производной системы, не относится к родительскому).
2. Примеры проблем с памятью
анализ:Метка не указывает родителя и не вызывает delete для него, поэтому это приведет к утечке памяти. Этот небольшой пример в книге также имеет проблему с памятью указателя. Улучшение: выделите объекты в стеке, а не в куче, следующим образом:
(1) Выделите объекты в стек вместо кучи:
(2) Установите бит флага, удалите метку после закрытия ():
(3) Ручное удаление после нового:
анализ:Программа аварийно завершает работу, потому что, когда метка закрыта, удалите & label; но объект метки - это пространство памяти, выделенное в стеке, и адрес в стеке удаления будет неправильным. Некоторые друзья понимают, что метка удаляется дважды, и это неправильно, вы можете проверить метку QLabel («Hello Qt!»); Label.show (); delete & label; При первом удалении вы получите ошибку.
анализ:Сбой при закрытии w. Внутри объекта есть список, в котором будут сохранены дочерние элементы, и указатель для сохранения родительского элемента. Когда он уничтожается, он удаляет и уничтожает всех дочерних элементов из родительского списка. w уничтожается до метки. Когда w уничтожается, метка объекта в списке детей удаляется, но метка выделяется в стеке, и возникает ошибка из-за объекта в стеке удаления.
(1) Отрегулируйте порядок, чтобы гарантировать, что метка будет уничтожена перед своим родителем. Когда метка будет уничтожена, она удалит себя из списка родительских объектов. Когда w будет уничтожена, дочерних элементов в списке дочерних элементов не будет. Объект.
(2) Присвойте метку куче
анализ:Программа заканчивается ненормально. Когда delete w удаляет метку, метка становится диким указателем (метка указывает на удаленный объект), вызывает label-> setText ("go"); Ошибка.
Улучшение: интеллектуальный указатель QPointer (QPointer может использоваться только для указания на объекты QObject и производного класса)
Когда QObject принимает очередь событий, если она была уничтожена вами на полпути, возникнет проблема, поэтому вам не следует удалять QObject непосредственно в QT. Если вы должны это сделать, используйте функцию deleteLater () QObject, он будет Пусть все события будут отправлены, и все будет очищено, как только будет очищена память, и даже если программа удаления вызывается несколько раз, проблем не будет.
Отправьте событие удаления в систему событий:
Это можно использовать.
QT умный указатель
QPointer - это шаблон класса. Он очень похож на обычный указатель, за исключением того, что QPointer может отслеживать динамически распределяемые объекты пространства и обновлять их во время удаления объектов.
Действительный принцип QPointer: в QPointer указатель QObject сохраняется, а указатель (двойной указатель) этого указателя передается управлению глобальной переменной, а QObject уничтожается (деструктор, QWidget через свой собственный деструктор, а не полагается на QObject) вызовет функцию QObjectPrivate :: clearGuards, чтобы установить глобальный двойной указатель GuardHash на * ноль, из-за проблемы двойных указателей, поэтому указатель в QPointer, конечно, Нуль. Судя по isNull пусто.
(2) Механизм автоматической сборки мусора QObjectCleanupHandler:
Очиститель объектов Qt является важной частью реализации автоматической сборки мусора. QObjectCleanupHandler может зарегистрировать множество подобъектов и автоматически удалять все подобъекты, когда вы удаляете его самостоятельно. В то же время он также может определить, были ли удалены какие-либо подобъекты, удалив его, таким образом, из своего списка подобъектов. Этот класс может использоваться для операций очистки классов, которые не находятся на одном уровне. Например, когда кнопка нажата, многие окна должны быть закрыты. Поскольку родительское свойство окна нельзя установить для кнопки другого окна, использование этого класса будет довольно Удобство.
Каждый программист, работающий с языком С++, должен уметь находить утечки памяти. Язык С++ - сложный язык, делать ошибки легко, а находить их бывает муторно. Особенно это касается утечек памяти. Ситуация с отловом утечек памяти только усугубляется, если в коде С++ используется библиотека Qt.
Эта статья посвящена разным инструментам, которые можно с той или иной степенью успешности применять для отлова утечек памяти в С++/Qt приложениях (desktop). Инструменты будут рассмотрены в связке с IDE Visual Studio 2019. В статье будут рассмотрены не все возможные инструменты, а лишь наиболее популярные и эффективные.
Наша команда давно и пристально изучает подобные инструменты и использует их в своей работе. Объем кода, на котором есть возможность проверить подобные инструменты, составляет около 1.5 миллиона строк. Опираясь на большой практический опыт, мы расскажем о плюсах и минусах разных инструментов, расскажем, что они способны найти, а что не по зубам, расскажем о неочевидных нюансах и, главное, составим сводную сравнительную таблицу по реальному примеру. Мы постараемся максимально быстро и просто ввести в курс дела (показать быстрый старт), потому даже если ты, читатель, никогда не занимался поиском утечек памяти, эта статья поможет за пару часов разобраться и найти свою первую утечку. Поехали!
В чем проблема?
Утечка памяти – ситуация, когда память была выделена (например, оператором new) и ошибочно не была удалена соответствующим оператором/функцией удаления (например, delete).
Пример 1.
Здесь налицо утечка при выделении памяти для первых 4 массивов. Утекает 160 байт. Последний массив удаляется корректно. Итак, утечка строго в одной строке:
Пример 2.
Здесь утечек уже больше: не удаляется память для a (400 байт), для b (1200 байт) и для test (16 байт для x64). Впрочем, удаление a и b в коде предусмотрено, но его не происходит из-за отсутствия вызова деструктора Test. Таким образом, утечек три, но ошибка, приводящая к этим утечкам, всего одна, и она порождается строкой
При этом в коде класса Test ошибок нет.
Пример 3.
Пусть есть класс Qt, примерно такой:
Пусть также где-то в коде затесалось выделение памяти:
Будет ли являться это утечкой, если явно не вызван delete? Это зависит от того, включен ли объект в иерархию объектов Qt. Если объект включён одним из следующих примерных вызовов, то нет, не утечка:
В остальных же случаях – утечка. Причем если мы будем считать точное количество утечек в этом примере, то можем наткнуться на неожиданный вывод: утечек больше, чем можно сначала предположить. Очевидная утечка – выделение памяти для InfoRectangle . Побочная утечка – выделение памяти для QTimer, несмотря на включение объекта _textSetTimer в иерархию объектов Qt. А вот утечка, которая совсем не очевидна – вызов функции connect .
Дело в том, что в ее реализации вызовом new всё же создается некий объект:
Таким образом, утечки памяти могут обнаруживаться в весьма неожиданных местах. А могут и вовсе не обнаруживаться, в зависимости от используемого инструментария. К примеру, если инструментарий не способен проникнуть внутрь кода Qt, то он не сможет ни обнаружить утечку в connect, ни разобраться с иерархиями Qt, а последнее, в свою очередь, уже чревато ложными срабатываниями.
Зафиксируем еще один важный момент: причина утечки одна, а самих утечек при этом может быть много. Этот нюанс важен при обсуждении инструментариев по отлову утечек. Идеальный инструментарий – тот, который, не давая ложных срабатываний и не пропуская утечек, указывает именно на первопричины утечек, то есть на каждую причину выдает ровно одну строку кода, ей соответствующую (а не целую кучу). То есть делает так, как делал бы человек. Забегая вперед, скажем, что таких инструментариев на данный момент не существует.
Раз их не существует, то как сравнивать между собой разные, не всегда коррелирующие результаты поиска утечек, полученные разными реальными инструментами? Мы ведь хотим сравнения…
Для этого мы взяли реальный пример – конкретную ревизию репозитория, для которой было точно известно, что утечки есть, и начали подробно с ней работать. Мы взяли один типичный сценарий работы пользователя с нашим приложением и начали на нём запускать все возможные динамические инструменты отлова утечек. Запускали многократно с разными настройками, детально анализируя полученные отчеты об ошибках. В итоге сформировали итоговый список ошибок в коде, приведших к утечкам памяти. Этот список назвали эталонным и посчитали, что других ошибок (ненайденных) нет. И в дальнейшем результаты, полученные каждой отдельной утилитой поиска утечек, сравнивали с эталонным списком ошибок.
Сценарий работы пользователя
Кол-во ошибок в эталоне
Суммарный объем утекающей памяти
Конкретный сценарий: запускаем ПО, жмем на кнопку 1, потом на кнопку 2, ждем завершения вычислений, закрываем ПО
Таблица 1. Эталон поиска утечек памяти.
Перейдем теперь непосредственно к обзору конкретных утилит, помогающих искать утечки памяти.
Intel Inspector
Intel Inspector – графическая утилита, удобно встраиваемая в Visual Studio и позволяющая в виде кликабельного списка выдавать места в коде с предполагаемыми утечками оперативной памяти проверяемого приложения и некоторыми другими проблемами памяти. В сценарии отлова утечек памяти Intel Inspector использует динамический анализ, а это значит, что если в процессе работы приложения код с утечками памяти не будет вызван, то и проблем в нем не будет найдено.
Установка
Intel Inspector входит в состав пакета Intel Parallel Studio 2019, при этом есть возможность установить только сам Intel Inspector, убрав галочки с остальных компонентов дистрибутива при установке. Visual Studio 2019 должна быть закрыта в момент установки Intel Parallel Studio. После установки, Intel Inspector будет автоматически встроен в Visual Studio и должен появиться на панели инструментов (рис. 1).
Рис. 1. Начало работы с Intel Inspector`ом
Если значок Intel Inspector’а не виден на панели инструментов, нужно щёлкнуть правой кнопкой мыши где-нибудь на этой панели инструментов и поставить галочку «Intel Inspector».
Запуск
При нажатии на кнопку-значок появится вкладка Intel Inspector с выбором глубины анализа. Выбираем первый пункт «Detect Leaks» и включаем все галочки, соответствующие всем видам анализа (рис. 2). Если какие-то галочки пропустить, то, к сожалению, есть риск, что не все утечки будут найдены.
Рис. 2. Вкладка Intel Inspector`а для его настройки и запуска
Далее нажимаем кнопку «Start», через некоторое время откроется приложение. В нем нужно запустить тот или иной сценарий работы, а лучше все сразу (то есть, как следует «погонять» приложение), затем закрыть. Чем больше на разных параметрах, в разных режимах и в разных сценариях проработает приложение, тем больше утечек памяти будет найдено. И это общий принцип для всех механизмов поиска утечек, использующих динамический анализ. Как мы уточнили ранее, в целях сравнения мы запускали только эталонный сценарий тестирования (см. табл. 1). Итак, после закрытия приложения Intel Inspector слегка задумывается и в итоге выдаёт отчёт следующего вида (рис. 3):
Рис. 3. Пример результатов анализа ПО на утечки памяти с помощью Intel Inspector.
В отчете выдаются кликабельный и сортируемый список утечек, размеры утечек, места в коде с утечками, call-stack и многое другое. Короче, форма выдачи результатов весьма и весьма на уровне. Все очень быстро понимается и усваивается. Все это – внутри IDE!
Это будет работать, если есть отладочная информация. То есть debug работать будет, а release нет. В С++-приложениях часто бывает так, что работа в режиме debug намного медленнее, чем в release (мы фиксировали разницу в скорости до 20 раз), и пользоваться debug'ом очень некомфортно. Однако на этот случай есть лайфхак – собрать версию release (быструю, со всеми ключами оптимизации), дополнительно включив в нее отладочную информацию. Это позволяет Intel Inspector'у подсветить строки в исходном коде, где он предполагает наличие утечек. О том, как включить в release отладочную информацию, написано здесь.
Результаты
Мы провели сравнение скоростных характеристик работы приложения в разных режимах работы: с Intel Inspector (будем называть его Инспектор) и без него, в debug и release. Тестирование проводилось на эталонном примере (см. табл 1).
Потом делаю расчеты с участием этой матрицы и так много раз в цикле. На каждой итерации цикла заполняю эту матрицу байтами из файла. Проблема в том, что в конечном итоге после около 3000 итераций цикла прога падает, по стеку ясно, что внутри системного вызова malloc падает, то есть из-за больших матриц память начинает так лагать.
Если я в конце итерации цикла удаляю эту матрицу так
Помощь в написании контрольных, курсовых и дипломных работ здесь
Как избежать утечки памяти при данном выделении памяти?
Всем привет. В проекте MFC делаю следующие манипуляции MY_STRUCT *ms = new MY_STRUCT();.
А как вы находите утечки памяти?
Люди! А как вы находите утечки памяти в Buildere? AQtime я попробовал, поставил профиль там.
Как проверить утечки памяти?
Люди добрые! Подайте пожалуйста неочень тяжелую (до 3 МВ) тулзу для проверки не теряет ли прога.
Самое очевидное решение:
std::unique_ptr , std::shared_ptr , читай, вникай. Это "Умные" указатели, благодаря которым тебе не придется сильно заморачиваться про утечки, так как они следят за тем, используется ли переменная, и если же нет - чистят память (за такое пояснение меня можно закидать тапками, пояснение нужно, чтобы передать общий концепт). Просто необходимая вещь в более-менее "взрослых" проектах
Отловить существующие - статические анализаторы. Например PVS Studio, можно пользоваться бесплатно при указании пары строк в начале каждого .cpp файла
Не использовать обычные указатели, использовать контейнеры, не использовать древнее malloc
roman912, не хотелось бы навязывать, но я бы стал использовать обычные библиотеки stl. Есть великолепная картинка, которая поможет вам определиться с контейнером. Можете найти аналог на Qt, скорее всего он будет существовать
А так - да, вроде должен подойти. Можете посмотреть краткие описания контейнеров для Qt и выбрать подходящий
Да, но вопрос откуда это посреди работы кода. Этот указатель на старте инициализируется и дальше после его наполнения только читается прогой для анализа. В какой-то момент на анализе он вот так 0xcdcdcdcd становится, как бы поймать где это происходит. Заметил, что если убирать qDebug по коду, то падает значительно реже.
Указатель на QList не нужен. Изучите основы C++.
Копай в сторону неинициализированной памяти. Вот пример кода, который выводит CDCDCDCD в MS Visual Studio
Согласен, что не нужен. Я QList * никогда сам не использовал. Но в этой проге через сигналы этот указатель посылается в кучу других классов для обработки. Видимо поэтому был выбран указатель на QList.
Также вопрос как правильно передавать QList без указателя в другой слот по значению или его адрес, если сделать без указателя объявление.
При передаче через сигнал тебе нужна копия или просто доступ к оригинальному объекту? Начни с этого. Ещё QList поддерживает implicit sharing.
Вообще-то доступ нужен, так как слот может добавлять в QList объекты.
слот может добавлять в QList объекты
Странная архитектура, ну да ладно. У тебя косяк с временем жизни объектов. Это нужно устранять, чтобы QList жил дольше объекта, в котором находится слот. Как костыль можно использовать не сырой указатель, а std::shared_ptr.
А я правильно понимаю, что самое простое в этом чужом проекте убрать указатели в QList и сделать его просто объектом. Я так понимаю, где-то идет разрушение кучи, которое задевает QList, а так хотя бы он на стеке будет выделен.
gdb -ex "help watch" -batch
А я правильно понимаю, что самое простое в этом чужом проекте убрать указатели в QList и сделать его просто объектом
Зависит от проекта. Если изменения в QList должны быть видимы для других частей кода, или в нем хранится большой массив данных и указатель при этом неконстантный, то так сделать не получится
Как костыль можно использовать не сырой указатель, а std::shared_ptr.
Тогда можно получить утечку памяти, только не как в названии темы, а настоящую
Читайте также: