to the top
close form
Для получения триального ключа
заполните форму ниже
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

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

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

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

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

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

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


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

Memory leak

05 Дек 2022

Утечка памяти или 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 также может выявлять многие ошибки утечек памяти. В отличие от динамических средств диагностики кода, он делает это не так точно. Зато он проверяет весь код программы и может найти утечки даже в редко используемом коде, который по тем или иным причинам не подвергается динамическому тестированию.

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

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

Популярные статьи по теме
C++ — язык 2022 года. Почему так, и что с другими языками?

Дата: 20 Янв 2023

Автор: Сергей Васильев

C++ становится языком 2022 года по версии TIOBE, обгоняя Python. Rust, C#, Go и прочие — далеко позади. Странно? Сейчас разберёмся.
PVS-Studio в 2022 году

Дата: 19 Янв 2023

Автор: Полина Алексеева

На дворе январь 2023, а значит, самое время подвести итоги уже прошлого 2022 года. Мы расскажем, чем занимались, и покажем, что нового появилось в анализаторе за это время. Давайте вместе взглянем на…
PVS-Studio: 2 фишки для быстрого старта

Дата: 08 Дек 2022

Автор: Сергей Васильев

В этой заметке расскажу, как легко начать использовать PVS-Studio. Рассмотрим два сценария: когда вы пробуете анализатор впервые и когда внедряете его в проект.
Почему ты делаешь за меня мою работу? Типы людей, которые не пишут в поддержку

Дата: 06 Дек 2022

Автор: Алёна Фоканова

Привлекательное название статьи должно раскрывать то, что будет в ней. Так вот, работа специалистом поддержки клиентов подразумевает появление вопросов к пользователю. Иногда возникает как раз такой:…
Как Apple и другие крупные компании настиг программный баг

Дата: 09 Ноя 2022

Автор: Ульяна Гришина

Сегодня мы отобрали свежие случаи программных ошибок, чтобы вы могли немного отвлечься и, возможно, узнать что-то новенькое. Если вам интересно узнать, как программисту удалось сломать Интернет по вс…

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

Следующие комментарии next comments
close comment form
Unicorn with delicious cookie
Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо