Что такое утечка памяти?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое утечка памяти?
Утечка памяти (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 сам позаботится
};
Правила для собеседования
- Никогда не используй сырые new/delete — только умные указатели
- Использй RAII — захват ресурса в конструкторе, освобождение в деструкторе
- std::unique_ptr — для единственного владельца
- std::shared_ptr — для общего владения (осторожно с циклами!)
- std::weak_ptr — для разрыва циклических ссылок
- Профилируй с Valgrind — никогда не отправляй код в production без проверки
Утечки памяти — одна из самых частых ошибок в C++. Правильное использование RAII и умных указателей на 99% решает проблему.