﻿# Да, PVS\-Studio умеет выявлять утечки памяти

Нас часто спрашивают, умеет ли статический анализатор кода PVS\-Studio выявлять утечки памяти \(memory leaks\)\. Чтобы много раз не писать похожие тексты в письмах, мы решили дать подробный ответ в блоге\. Да, PVS\-Studio умеет выявлять утечки памяти и других ресурсов\. Для этого в PVS\-Studio реализовано несколько диагностик и в статье будут продемонстрированы примеры обнаружения ошибок в реальных проектах\.

![0543_Memory_leaks_ru/image1.png](https://import.viva64.com/docx/blog/0543_Memory_leaks_ru/image1.png)

## Выявление утечек памяти и ресурсов

Утечка памяти \([memory leak](https://en.wikipedia.org/wiki/Memory_leak)\) \- это процесс неконтролируемого уменьшения объёма свободной оперативной или виртуальной памяти компьютера, связанный с ошибками в работающих программах, вовремя не освобождающих ненужные уже участки памяти\. Согласно CWE утечки памяти классифицируются как дефект [CWE\-401](https://cwe.mitre.org/data/definitions/401.html)\.

Утечки памяти являются одной из разновидностей ошибок утечек ресурсов \([resource leak](https://en.wikipedia.org/wiki/Resource_leak)\)\. Примером утечки другого вида ресурса может служить ошибка, именуемая утечкой файлового дескриптора: файл открывается, но не закрывается, и дескриптор не возвращается операционной системе\. Согласно CWE такие ошибки можно классифицировать как [CWE\-404](https://cwe.mitre.org/data/definitions/404.html)\.

Утечки памяти или других ресурсов могут приводить к ошибкам отказа обслуживания \([Denial of Service](https://cwe.mitre.org/data/definitions/730.html)\)\.

Для выявления утечек памяти и других ресурсов используются инструменты [динамического](https://pvs-studio.ru/ru/blog/terms/0070/) и [статического](https://pvs-studio.ru/ru/blog/terms/0046/) анализа кода\. К числу таких инструментов принадлежит и анализатор PVS\-Studio\.

Для выявления рассматриваемого класса ошибок в PVS\-Studio реализованы следующие диагностики:

* [V599](https://pvs-studio.ru/ru/docs/warnings/v599/)\. The virtual destructor is not present, although the 'Foo' class contains virtual functions\.
* [V680](https://pvs-studio.ru/ru/docs/warnings/v680/)\. The 'delete A, B' expression only destroys the 'A' object\. Then the ',' operator returns a resulting value from the right side of the expression\.
* [V689](https://pvs-studio.ru/ru/docs/warnings/v689/)\. The destructor of the 'Foo' class is not declared as a virtual\. It is possible that a smart pointer will not destroy an object correctly\.
* [V701](https://pvs-studio.ru/ru/docs/warnings/v701/)\. realloc\(\) possible leak: when realloc\(\) fails in allocating memory, original pointer is lost\. Consider assigning realloc\(\) to a temporary pointer\.
* [V772](https://pvs-studio.ru/ru/docs/warnings/v772/)\. Calling a 'delete' operator for a void pointer will cause undefined behavior\.
* [V773](https://pvs-studio.ru/ru/docs/warnings/v773/)\. The function was exited without releasing the pointer/handle\. A memory/resource leak is possible\.
* [V779](https://pvs-studio.ru/ru/docs/warnings/v779/)\. Unreachable code detected\. It is possible that an error is present\.
* [V1002](https://pvs-studio.ru/ru/docs/warnings/v1002/)\. A class, containing pointers, constructor and destructor, is copied by the automatically generated operator\= or copy constructor\.
* [V1005](https://pvs-studio.ru/ru/docs/warnings/v1005/)\. The resource was acquired using 'X' function but was released using incompatible 'Y' function\.

## Примеры

Давайте рассмотрим несколько примеров обнаружения анализатором PVS\-Studio утечек памяти в коде открытых проектов\.

**Пример N1\.**

Проект NetDefender\. Предупреждение PVS\-Studio: V773 The 'm\_pColumns' pointer was not released in destructor\. A memory leak is possible\. fireview\.cpp 95

Обратите внимание, что в конструкторе создаются два объекта:

* Указатель на объект типа _CBrush_ сохраняется в переменной _m\_pBrush\._
* Указатель на объект типа _CStringList_ сохраняется в переменной _m\_pColumns_\.

```cpp
CFireView::CFireView() : CFormView(CFireView::IDD)
{
  m_pBrush = new CBrush;
  ASSERT(m_pBrush);
  m_clrBk = RGB(148, 210, 252);
  m_clrText = RGB(0, 0, 0);
  m_pBrush->CreateSolidBrush(m_clrBk);

  m_pColumns = new CStringList;
  ASSERT(m_pColumns);
  _rows = 1;
  start = TRUE;
  block = TRUE;
  allow = TRUE;
  ping = TRUE;
  m_style=StyleTile;
}
```

В деструкторе же, происходит уничтожение только одного объекта, адрес которого хранится в переменной _m\_pBrush_:

```cpp
CFireView::~CFireView()
{
  if(m_pBrush)
  {
     delete m_pBrush;
  }
}
```

Про переменную _m\_pColumns_ видимо просто забыли\. В результате возникает утечка памяти\.

**Пример N2\.**

Проект Far2l \(Linux port of FAR v2\)\. Рассматриваема ошибка интересна тем, что она обнаруживается сразу двумя различными диагностиками PVS\-Studio:

* V779 Unreachable code detected\. It is possible that an error is present\. 7z\.cpp 203
* V773 The function was exited without releasing the 't' pointer\. A memory leak is possible\. 7z\.cpp 202

```cpp
BOOL WINAPI _export SEVENZ_OpenArchive(const char *Name,
                                       int *Type)
{
  Traverser *t = new Traverser(Name);
  if (!t->Valid())
  {
    return FALSE;
    delete t;
  }

  delete s_selected_traverser;
  s_selected_traverser = t;
  return TRUE;
}
```

Местами перепутаны оператор _return_ и оператор _delete_\. В результате оператор _delete_ никогда не выполняется\. Анализатор предупреждает об этом, выдавая сообщение о недостижимом коде и сообщение об утечке памяти\. 

**Пример N3\.**

Проект Firebird\. Предупреждение PVS\-Studio: V701 realloc\(\) possible leak: when realloc\(\) fails in allocating memory, original pointer 's\-\>base' is lost\. Consider assigning realloc\(\) to a temporary pointer\. mstring\.c 42

```cpp
int mputchar(struct mstring *s, int ch)
{
  if (!s || !s->base) return ch;
  if (s->ptr == s->end) {
    int len = s->end - s->base;
    if ((s->base = realloc(s->base, len+len+TAIL))) {
      s->ptr = s->base + len;
      s->end = s->base + len+len+TAIL; }
    else {
      s->ptr = s->end = 0;
      return ch;
    }
  }
  *s->ptr++ = ch;
  return ch;
}
```

Рассматриваемая функция предназначена для добавления к строке символа\. Буфер, который используется для хранения строки, увеличивается с помощью вызова функции _realloc_\. Ошибка заключается в том, что если функция _realloc_ не может увеличить размер буфера памяти, то произойдёт утечка памяти\. Причина в том, что если нет блока памяти достаточного размера, то функция _realloc_ возвращает _NULL_ и при этом она не освобождает предыдущий буфер памяти\. Поскольку результат работы функции сразу записывается в переменную _s\-\>base_, то нет никакой возможности освободить ранее выделенный блок\.

Ошибку можно исправить, добавив временную переменную и вызов функции _free_:

```cpp
int mputchar(struct mstring *s, int ch)
{
  if (!s || !s->base) return ch;
  if (s->ptr == s->end) {
    void *old = s->base;
    int len = s->end - s->base;
    if ((s->base = realloc(s->base, len+len+TAIL))) {
      s->ptr = s->base + len;
      s->end = s->base + len+len+TAIL; }
    else {
      free(old);
      s->ptr = s->end = 0;
      return ch;
    }
  }
  *s->ptr++ = ch;
  return ch;
}
```

## Статический и динамический анализ

На примере PVS\-Studio видно, что статические анализаторы умеют выявлять различные виды утечек ресурсов\. Однако, ради справедливости следует отметить, что в целом статические анализаторы проигрывают в сфере поиска утечек динамическим анализаторам кода\.

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

Динамическим анализаторам найти утечки памяти или ресурсов намного проще\. Им не надо ничего отслеживать\. Им надо только запомнить место в программе, где какой\-то ресурс выделен и проверить, освободится ли он до окончания программы\. Если нет, то найдена ошибка\. Таким образом, динамические анализаторы точнее и надёжнее обнаруживают различные виды утечек\.

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

Не следует противопоставлять статический и динамический анализ\. Эти методики не конкурируют, а дополняют друг друга\. Решая вопросы повышения качества и надёжности кода, следует использовать инструменты обоих типов\. Про это я много раз писал и мне не хочется повторяться\. Тем, кто хочет разобраться в этом вопросе подробнее, предлагаю несколько ссылок:

* [Статический и динамический анализ кода](https://pvs-studio.ru/ru/blog/posts/0248/);
* [Мифы о статическом анализе\. Миф третий – динамический анализ лучше чем статический](https://pvs-studio.ru/ru/blog/posts/0117/);
* [Valgrind \- это хорошо, но недостаточно](https://pvs-studio.ru/ru/blog/posts/cpp/0278/);
* [Проверяем код динамического анализатора Valgrind с помощью статического анализатора](https://pvs-studio.ru/ru/blog/posts/cpp/0504/)\.

## Заключение

Статический анализатор кода PVS\-Studio способен выявить широкий спектр ошибок, связанных с утечками памяти и других ресурсов\. Используйте его регулярно, чтобы устранять ошибки ещё на этапе написаний кода или во время ночных сборок проекта:

* [Режим инкрементального анализа PVS\-Studio](https://pvs-studio.ru/ru/docs/manual/0024/);
* [Прямая интеграция анализатора в системы автоматизации сборки \(C/C\+\+\)](https://pvs-studio.ru/ru/docs/manual/0006/)

Команда PVS\-Studio желает Вам безбажного кода\.