← Назад к вопросам

Что такое утечка памяти?

2.0 Middle🔥 201 комментариев
#Умные указатели и управление памятью#Язык C++

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

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое утечка памяти?

Утечка памяти (memory leak) — это ошибка программирования, при которой приложение выделяет память динамически (с помощью new, malloc), но забывает её освобождать (с помощью delete, free). Со временем такая память накапливается, что приводит к исчерпанию всей доступной памяти и краху программы.

Простой пример утечки

void memory_leak_example() {
    int* ptr = new int(42);  // Выделяем память
    std::cout << *ptr << std::endl;
    // Забыли delete ptr; --- УТЕЧКА!
    // Функция завершается, указатель уничтожается, но память в куче остаётся
}

int main() {
    for (int i = 0; i < 1000000; i++) {
        memory_leak_example();  // Каждый вызов = +4 байта утекает
    }
    // После 1 млн вызовов: 4 МБ памяти потеряно
}

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

Типы утечек памяти

1. Явная утечка (Classic Leak)

void classic_leak() {
    int* arr = new int[100];
    // ... используем arr ...
    // delete[] arr;  // Забыли удалить!
}

2. Утечка в исключениях

void exception_leak() {
    int* data = new int(10);
    if (data == nullptr) {
        throw std::runtime_error("Error");
        // delete data;  // Если throw срабатывает раньше delete
    }
    delete data;
}
// Решение: try-catch или RAII

3. Утечка циклических ссылок (циклических указателей)

class Node {
public:
    std::shared_ptr<Node> next;
};

std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();

node1->next = node2;
node2->next = node1;  // ЦИКЛИЧЕСКАЯ ССЫЛКА!

// Когда node1 и node2 выходят из области видимости,
// shared_ptr считает счётчик ссылок:
// node1: ref_count = 2 (node1 и node2->next)
// node2: ref_count = 2 (node2 и node1->next)
// Оба > 0, поэтому деструкторы не вызываются -> УТЕЧКА

// Решение: использовать std::weak_ptr
class Node {
public:
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // weak_ptr не увеличивает счётчик
};

4. Утечка в строках и контейнерах

void string_leak() {
    char* str = new char[1000];
    strcpy(str, "Hello");  // Опасно: buffer overflow
    // delete[] str;  // Забыли удалить
}

// Правильно: используй std::string
void no_leak() {
    std::string str = "Hello";  // RAII: автоматически управляет памятью
}  // Деструктор std::string удалит память автоматически

Как обнаружить утечки?

1. Valgrind (Linux)

g++ -g program.cpp -o program
valgrind --leak-check=full ./program

# Output:
# HEAP SUMMARY:
# definitely lost: 400 bytes in 1 blocks  <- УТЕЧКА!
# indirectly lost: 0 bytes

2. AddressSanitizer (компилятор инструмент)

g++ -g -fsanitize=address program.cpp -o program
./program

# Runtime detection с красивым报告

3. Инструмент Dr. Memory (Windows)

dr_memory program.exe

4. Профилировщик памяти в IDE (Visual Studio, Xcode)

Как избежать утечек?

1. RAII (Resource Acquisition Is Initialization) — основной принцип

// Плохо: ручное управление
int* ptr = new int(42);
delete ptr;  // Легко забыть

// Хорошо: RAII через умные указатели
std::unique_ptr<int> ptr(new int(42));
// или
std::unique_ptr<int> ptr = std::make_unique<int>(42);
// Деструктор unique_ptr вызовет delete автоматически

2. std::unique_ptr (для эксклюзивного владения)

class MyClass {
private:
    std::unique_ptr<Data> data;
public:
    MyClass() : data(std::make_unique<Data>()) { }
    // Деструктор MyClass вызовет delete data автоматически
};

3. std::shared_ptr (для общего владения)

std::shared_ptr<int> ptr1(new int(10));
std::shared_ptr<int> ptr2 = ptr1;  // Счётчик ссылок = 2
// Когда оба выходят из области видимости -> автоматический delete

4. STL контейнеры (std::vector, std::string, std::map)

void no_leaks() {
    std::vector<int> vec;  // RAII контейнер
    vec.push_back(new int(42));  // Плохо: смешиваем!
    
    // Правильно:
    std::vector<std::unique_ptr<int>> vec;
    vec.push_back(std::make_unique<int>(42));
    // При очистке vec все unique_ptr удалят свои данные
}

5. try-catch и RAII

void safe_function() {
    std::unique_ptr<int> data = std::make_unique<int>(10);
    try {
        if (something_wrong) {
            throw std::runtime_error("Error");
            // Даже при throw unique_ptr вызовет деструктор!
        }
        use_data(data.get());
    } catch (...) {
        // data автоматически очищена
        throw;
    }
    // data удалена при выходе из области видимости
}

Практический пример утечки и её исправления

// УТЕЧКА
class ImageProcessor {
private:
    unsigned char* buffer;
public:
    ImageProcessor(int size) {
        buffer = new unsigned char[size];
    }
    
    ~ImageProcessor() {
        // delete[] buffer;  // Забыли!
    }
};

// ИСПРАВЛЕННЫЙ ВАРИАНТ
class ImageProcessor {
private:
    std::unique_ptr<unsigned char[]> buffer;
public:
    ImageProcessor(int size) 
        : buffer(std::make_unique<unsigned char[]>(size)) { }
    
    // Деструктор не нужен, unique_ptr сам позаботится
};

Правила для собеседования

  1. Никогда не используй сырые new/delete — только умные указатели
  2. Использй RAII — захват ресурса в конструкторе, освобождение в деструкторе
  3. std::unique_ptr — для единственного владельца
  4. std::shared_ptr — для общего владения (осторожно с циклами!)
  5. std::weak_ptr — для разрыва циклических ссылок
  6. Профилируй с Valgrind — никогда не отправляй код в production без проверки

Утечки памяти — одна из самых частых ошибок в C++. Правильное использование RAII и умных указателей на 99% решает проблему.