﻿# Memory leak

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

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

Подобные ошибки в некоторых случаях можно рассматривать в качестве потенциальных уязвимостей для [DoS\-атак](https://pvs-studio.ru/ru/blog/terms/6585/) уровня приложения\. Суть этой атаки — исчерпать лимиты ресурсов системы\.

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

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

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

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

В C\+\+ динамическая память выделяется при помощи оператора [_new_](https://en.cppreference.com/w/cpp/memory/new/operator_new)\. В C функциями: [_malloc_](https://en.cppreference.com/w/c/memory/malloc), [_realloc_](https://en.cppreference.com/w/c/memory/realloc), [_calloc_](https://en.cppreference.com/w/c/memory/calloc), [_strdup_](https://en.cppreference.com/w/c/experimental/dynamic/strdup) и так далее\. Освободить блок памяти можно при помощи оператора [_delete_](https://en.cppreference.com/w/cpp/memory/new/operator_delete) \(C\+\+\) или функции [_free_](https://en.cppreference.com/w/c/memory/free) \(C\)\.



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

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

```cpp
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"\)_\.

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

```cpp
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](https://pvs-studio.ru/ru/blog/posts/cpp/0938/)"\.

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

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

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

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

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

Такие языки, как C и C\+\+, предполагают ручное управление памятью, в отличие от C\# и Java, где очисткой управляемой кучи занимается функция сборщика мусора \(Garbage Collector\)\. Однако в C\+\+ существуют определённые механизмы защиты от утечек памяти\. Они реализуются с помощью "умных" указателей, таких как [_std::unique\_ptr_](https://en.cppreference.com/w/cpp/memory/unique_ptr), [_std::shared\_ptr_](https://en.cppreference.com/w/cpp/memory/shared_ptr) и так далее\.

Когда требуется убедиться, что в программе нет никаких утечек, для отлова ошибок используют инструменты динамического анализа\. Например, в Visual Studio можно воспользоваться библиотекой [Debug CRT](https://docs.microsoft.com/ru-ru/visualstudio/debugger/crt-debug-library-use?view=vs-2022)\. В точке завершения программы вызывается функция _\_CrtDumpMemoryLeaks,_ и, как только отработает отладчик, в окне "Вывод" блока "Отладка" можно посмотреть отчёт об утечках\. Эта библиотека проверяет любое выделение памяти через _new_ или функцию _malloc_\.

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

На Unix\-подобных операционных системах \(Linux, macOS\) можно воспользоваться инструментом [_Leak Sanitizer_](https://clang.llvm.org/docs/LeakSanitizer.html)\. Начиная с 2019 года, он также [доступен](https://devblogs.microsoft.com/cppblog/addresssanitizer-asan-for-windows-with-msvc/) и в Windows\. Этот санитайзер интегрирован в более продвинутый [_Address Sanitizer_](https://clang.llvm.org/docs/AddressSanitizer.html), однако его также можно подключить автономно\. Для того чтобы воспользоваться санитайзером в своей программе, необходимо скомпилировать её с флагами _\-fsanitize\=leak_ в автономном режиме или _\-fsanitize\=address_ в комбинации с _Address Sanitizer_\. Столкнувшись с утечкой памяти, инструмент сохранит информацию о ней в стандартный поток вывода ошибок\.

Статический анализатор PVS\-Studio также [может выявлять](https://pvs-studio.ru/ru/blog/posts/cpp/0543/) многие ошибки утечек памяти\. В отличие от динамических средств диагностики кода, он делает это не так точно\. Зато он проверяет весь код программы и может найти утечки даже в редко используемом коде, который по тем или иным причинам не подвергается динамическому тестированию\. 

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

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

1. Wikipedia, [Memory leak](https://en.wikipedia.org/wiki/Memory_leak)\.
1. Standard C\+\+ Foundation\. [Memory Management](https://isocpp.org/wiki/faq/freestore-mgmt)\.
1. [Memory Management Lecture \- Breda University of applied sciences \- Games](https://gist.github.com/simonrenger/d1da2a10d11f8a971fc6f1b574ab3e99)\.
1. [How to detect errors and bugs in code? Explaining Memory Leaks in C\+\+](https://youtu.be/AeyTSLGIX1M)\.
1. [Да, PVS\-Studio умеет выявлять утечки памяти](https://pvs-studio.ru/ru/blog/posts/cpp/0543/)\.