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

Как отлавливал утечки памяти?

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

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

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

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

Методы отлова утечек памяти в C/C++

В своей практике использовал многоуровневый подход для выявления и устранения утечек памяти. Вот мой алгоритм работы.

1. Статические анализаторы (первая линия защиты)

AddressSanitizer (ASan) — встроенный в компиляторы, работает при запуске

# Компиляция с AddressSanitizer
gcc -fsanitize=address -g program.cpp -o program

# Запуск с отчётом
./program
# Вывод содержит точное место утечки с stacktrace
int* p = new int[100];
std::cout << "Working..." << std::endl;
// Забыли delete — AddressSanitizer сразу найдёт

// Вывод:
// =================================================================
// ==12345==ERROR: LeakSanitizer: detected memory leaks
// Direct leak of 400 byte(s) in 1 object(s)
// #0 0x... in operator new (/path/to/program+0x...)
// #1 0x... in main (/path/to/program.cpp:5)

MemorySanitizer (MSan) — для обнаружения чтения неинициализированной памяти

gcc -fsanitize=memory -g program.cpp -o program

Clang Static Analyzer

clang --analyze program.cpp  # Анализирует без запуска

2. Valgrind — инструмент для production диагностики

Запуск под Valgrind:

valgrind --leak-check=full --show-leak-kinds=all ./program

Пример вывода:

==12345== 100 bytes in 1 blocks are definitely lost in loss record 1
==12345==    at 0x4C2D1AA: malloc (vg_replace_malloc.c:287)
==12345==    by 0x400644: main (program.cpp:10)
==12345== 
==12345== LEAK SUMMARY:
==12345==    definitely lost: 100 bytes in 1 blocks

Для более точного анализа:

# С дополнительной информацией
valgrind --leak-check=full --track-origins=yes ./program

# Для программ, читающих с stdin
valgrind ./program < input.txt

3. Инструмент profiling: Massif (часть Valgrind)

valgrind --tool=massif --massif-out-file=massif.out ./program
ms_print massif.out  # Красивый вывод используемой памяти

Анализирует:

  • Пиковое использование памяти
  • Где именно алокируется память
  • Как растёт использование со временем

4. Smart pointers — предотвращение утечек

Вместо raw pointers:

// ПЛОХО: может быть утечка
int* p = new int[1000];
process(p);
// Если process() выбросит исключение — утечка!

// ХОРОШО: автоматическое управление
std::unique_ptr<int[]> p = std::make_unique<int[]>(1000);
process(p.get());
// Деструктор вызовется автоматически, даже при исключении

5. RAII паттерн

class Resource {
    int* data;
public:
    Resource(size_t size) {
        data = new int[size];
    }
    
    ~Resource() {  // Гарантированно вызовется!
        delete[] data;
    }
    
    // Запретить копирование
    Resource(const Resource&) = delete;
    Resource& operator=(const Resource&) = delete;
};

void process() {
    {
        Resource res(1000);
        do_work(res);
        // Деструктор вызовется при выходе из области видимости
    }
    // res полностью очищен, даже если было исключение
}

6. Ручной анализ кода (code review)

Что искать:

// ОПАСНО 1: delete без соответствующего new
void process() {
    int* p;  // Не инициализирован!
    delete p;  // Undefined behavior
}

// ОПАСНО 2: double free
void process() {
    int* p = new int(5);
    delete p;
    p = nullptr;  // Хорошо, но...
    delete p;  // delete nullptr безопасно, но код запутанный
}

// ОПАСНО 3: утечка при исключении
void process() {
    int* p = new int(5);
    dangerous_function();  // Может выбросить
    delete p;  // Никогда не выполнится!
}

// ПРАВИЛЬНО: RAII или smart pointer
void process() {
    std::unique_ptr<int> p = std::make_unique<int>(5);
    dangerous_function();  // Даже если выброшено
    // p автоматически удалится
}

7. Логирование алокаций

#include <cstdlib>
#include <iostream>

void* operator new(size_t size) {
    void* p = malloc(size);
    std::cerr << "Allocated " << size << " bytes at " << p << std::endl;
    return p;
}

void operator delete(void* p) noexcept {
    std::cerr << "Deallocated " << p << std::endl;
    free(p);
}

// Вывод все new/delete вызовов
// Затем парсим и ищем несоответствия

8. Процедура работы при поиске утечки

Шаг 1: Запущу программу с AddressSanitizer

make clean && make CFLAGS="-fsanitize=address -g"
./program < test_input.txt

Шаг 2: Анализирую stacktrace из вывода

ASan показывает точную строку кода, где выделена неосвобождённая память.

Шаг 3: Если ASan не помог, используем Valgrind

valgrind --leak-check=full --track-origins=yes ./program < test_input.txt

Шаг 4: Профилирую с Massif для анализа тренда

valgrind --tool=massif ./program < large_input.txt
ms_print massif.out

Шаг 5: Code review смежного кода

Искал raw pointers, delete, циклические ссылки в shared_ptr.

Шаг 6: Unit тесты с инструментами

# Каждый тест прогоняем под ASan
make test ASAN=1

9. Специфические утечки

Утечка циклических ссылок:

struct Node {
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;  // Циклическая ссылка!
};

auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->prev = a;  // Цикл! Оба объекта не удалятся

// Решение: использовать std::weak_ptr
struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // Не держит жизнь объекта
};

Утечка static объектов:

std::string& get_global_string() {
    static std::string* s = new std::string("test");  // УТЕЧКА!
    return *s;  // Никогда не удалится
}

// Правильно
std::string& get_global_string() {
    static std::string s("test");  // Stack-based, не утечка
    return s;
}

10. Интеграция в CI/CD

#!/bin/bash
# check_leaks.sh

valgrind --leak-check=full --error-exitcode=1 ./program
if [ $? -ne 0 ]; then
    echo "Memory leaks detected!"
    exit 1
fi
echo "No leaks found"
exit 0

В GitHub Actions / GitLab CI:

test:
    script:
        - apt-get install -y valgrind
        - gcc -g program.c -o program
        - valgrind --leak-check=full ./program
        - if [ $? -ne 0 ]; then exit 1; fi

11. Best Practices из опыта

1. Предпочитай Stack Allocation:

// Хорошо
std::vector<int> v(1000);  // На стеке (auto cleanup)

// Плохо
int* v = new int[1000];    // На heap

2. Используй RAII везде:

std::unique_ptr<T> p;      // Автоматическая очистка
std::shared_ptr<T> p;      // Reference counting
std::lock_guard<mutex> l;  // Автоматический unlock

3. Запрети default операции для классов с ресурсами:

class MyResource {
public:
    MyResource() = default;
    ~MyResource();
    MyResource(const MyResource&) = delete;  // Запретить копию
    MyResource& operator=(const MyResource&) = delete;
};

Резюме

Мой процесс отлова утечек памяти:

  1. AddressSanitizer — быстро выявить на dev машине
  2. Valgrind — глубокий анализ в production like окружении
  3. Code review — поиск known patterns утечек
  4. RAII и smart pointers — профилактика
  5. Автоматизация — CI/CD интеграция
  6. Мониторинг — отслеживание использования памяти

Этот многоуровневый подход позволяет практически исключить утечки памяти в production.

Как отлавливал утечки памяти? | PrepBro