Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top
close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте, отфильтровано ли письмо в одну из следующих стандартных папок:

  • Промоакции
  • Оповещения
  • Спам

Вебинар: Парсим С++ - 25.10

Memory leak

06 Июн 2024

Утечка памяти или memory leak – это ошибка в исходном коде, при которой выделенная под переменную, массив, объект класса и т. д. динамическая память не освобождается и впоследствии теряется, а данные так и остаются в оперативной памяти до момента закрытия программы.

Подобные ошибки могут привести к сильной нагрузке на компьютер и значительно уменьшить его производительность во время работы приложения. Кроме того, если программа начнёт бесконечно потреблять память, то операционная система в какой-то момент аварийно завершит работу проблемного приложения и всех связанных с ним процессов.

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

Причины утечки памяти

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

Однако размер стека довольно ограничен, и программа может запросить дополнительную память, которая ещё не была распределена операционной системой. Подобная динамическая память называется управляемой кучей (heap).

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

В C++ динамическая память выделяется при помощи оператора new. В C функциями: malloc, realloc, calloc, strdup и так далее. Освободить блок памяти можно при помощи оператора delete (C++) или функции free (C).

В больших проектах указатели постоянно передаются между функциями, структурами, классами, и, как следствие, довольно легко запутаться и не уследить за всеми аспектами выделения и освобождения памяти. Эта сложность и провоцирует возникновение ошибок утечек памяти.

Рассмотрим пример утечки памяти, найденной с помощью анализатора PVS-Studio в проекте Augeas:

static void xfm_error(struct tree *xfm, const char *msg) {
  char *v = msg ? strdup(msg) : NULL;
  char *l = strdup("error");

  if (l == NULL || v == NULL)
    return;
  tree_append(xfm, l, v);
}

Несмотря на то, что функция маленькая, она может привести сразу к трём сценариям утечки памяти. Два сценария маловероятны, а третий вполне реальный.

Первые два сценария. Один из вызовов функций strdup вернёт NULL. В таком случае функция досрочно завершит работу, и будет потерян указатель, который вернул другой вызов strdup. Это хоть и маловероятные сценарии, но вполне возможные.

Третий более вероятный сценарий. Функция xfm_error рассчитана на то, что значение аргумента msg может быть равно NULL. В этом случае функция ничего не делает и досрочно завершает свою работу. Однако при этом будет потеряна память, выделенная вызовом strdup("error").

Чтобы избежать всех этих ошибок, код можно переписать следующим образом:

static void xfm_error(struct tree *xfm, const char *msg) {
  if (msg == NULL)
    return;
  char *v = strdup(msg);
  if (v == NULL)
    return;
  char *l = strdup("error");
  if (l == NULL) {
    free(v);
  }
  tree_append(xfm, l, v);
}

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

Примечание. См. также релевантную тему "Четыре причины проверять, что вернула функция malloc".

Последствия утечки памяти

Если не очищать данные в динамической памяти – оперативная память может исчерпаться, и по достижению доступных лимитов операционная система попросту завершит процесс нашей программы самостоятельно, не дав сохранить проделанную работу. Когда подобное аварийное закрытие не настроено в ОС (например, в некоторых дистрибутивах Linux, где для этого дополнительно требуется скачивать специальную утилиту), компьютер может зависнуть на длительное время или сильно замедлиться. В итоге его использование станет невозможным, а для корректного закрытия программы потребуется долго ждать её отклика. Иногда же устройство может замедлиться так сильно, что ничего не останется, кроме как перезагрузить его.

Куда хуже, если программа функционирует в связке с другими приложениями. В таком случае завершиться могут все связанные с ней процессы, в том числе применяемые пользователем параллельно для собственных нужд.

Это приведёт к потере информации, не только размещаемой в программе, но и той, с которой пользователь работал отдельно и не сохранял. Например, когда программа фоном использует редакторы и прочие сторонние инструменты.

Как избежать утечки памяти

Такие языки, как C и C++, предполагают ручное управление памятью, в отличие от C# и Java, где очисткой управляемой кучи занимается функция сборщика мусора (Garbage Collector). Однако в C++ существуют определённые механизмы защиты от утечек памяти. Они реализуются с помощью "умных" указателей, таких как std::unique_ptr, std::shared_ptr и так далее.

Когда требуется убедиться, что в программе нет никаких утечек, для отлова ошибок используют инструменты динамического анализа. Например, в Visual Studio можно воспользоваться библиотекой Debug CRT. В точке завершения программы вызывается функция _CrtDumpMemoryLeaks, и, как только отработает отладчик, в окне "Вывод" блока "Отладка" можно посмотреть отчёт об утечках. Эта библиотека проверяет любое выделение памяти через new или функцию malloc.

Можно воспользоваться инструментами от сторонних разработчиков, которые к тому же неплохо интегрируются со средой Visual Studio. Например, Visual Leak Detector – всё в том же окне вывода программа сообщает обо всех файлах и строчках кода, в которых произошли утечки.

На Unix-подобных операционных системах (Linux, macOS) можно воспользоваться инструментом Leak Sanitizer. Начиная с 2019 года, он также доступен и в Windows. Этот санитайзер интегрирован в более продвинутый Address Sanitizer, однако его также можно подключить автономно. Для того чтобы воспользоваться санитайзером в своей программе, необходимо скомпилировать её с флагами -fsanitize=leak в автономном режиме или -fsanitize=address в комбинации с Address Sanitizer. Столкнувшись с утечкой памяти, инструмент сохранит информацию о ней в стандартный поток вывода ошибок.

Статический анализатор PVS-Studio также может выявлять многие ошибки утечек памяти. В отличие от динамических средств диагностики кода, он делает это не так точно. Зато он проверяет весь код программы и может найти утечки даже в редко используемом коде, который по тем или иным причинам не подвергается динамическому тестированию.

В случаях, когда утечку не удаётся исправить, например она обнаружена в сторонней библиотеке, к которой не имеется доступа, следует написать баг-репорт разработчику, попробовать обновить версию или поискать сторонние аналоги. Если же без подключения такой библиотеки никак не обойтись – можно вынести выполнение связанных с ней функций в отдельное приложение, которое будет работать только в моменты необходимости, при этом, конечно, создавая утечки, но контролируемые с помощью команд запуска и завершения процесса.

Дополнительные ссылки:

Популярные статьи по теме


Комментарии (0)

Следующие комментарии next comments
close comment form