Глиф Учебники

Утечка памяти: как найти и исправить

Справочник · C · 2 минуты чтения

Утечка памяти — память, выделенная через malloc, calloc или realloc, больше не нужна, но не освобождена через free. Программа не падает, просто занимает всё больше памяти. Для короткой учебной программы это почти незаметно — после её завершения система забирает всю память. Но в программе, которая работает долго или выделяет память в цикле, утечка однажды съедает всю память.

Правило одно: на каждый успешный malloc (calloc, realloc) — ровно один free, когда память больше не нужна.

Как появляются утечки

Забытый free в цикле или функции.

C
#include <stdlib.h>

void work(int n) {
    int *tmp = malloc(n * sizeof(int));
    if (tmp == NULL) return;
    for (int i = 0; i < n; i++) tmp[i] = i;
    // free(tmp) забыт: при каждом вызове теряется n * 4 байт
}

Потерянный указатель. В указатель записали новый адрес, а старый блок не освободили:

C
int *p = malloc(100 * sizeof(int));
p = malloc(200 * sizeof(int));   // первый блок больше недоступен

realloc в тот же указатель. p = realloc(p, size) при нехватке памяти вернёт NULL — старый блок останется занятым, а его адрес потерян. Правильно — через временный указатель (пример в уроке Динамическая память и файлы).

Ранний выход. return посреди функции при ошибке, а free стоит только в конце. Перед каждым выходом освобождайте то, что уже выделено.

Структура с указателями внутри. free освобождает только тот блок, адрес которого получил. Если в структуре есть указатели на другие выделенные блоки, их освобождают раньше, чем саму структуру.

Как найти утечку

Подойдёт любой из двух инструментов: valgrind проверяет уже собранную программу, санитайзер встраивается в неё при сборке. Оба работают в Linux.

valgrindотдельная программа-проверка

Поставьте его (sudo apt install valgrind), соберите программу с -g (ключ добавляет сведения о строках программы, чтобы отчёт показывал номера строк) и запустите через valgrind:

Bash
gcc -g prog.c -o prog
valgrind --leak-check=full ./prog

В конце отчёта — раздел LEAK SUMMARY. Строка definitely lost: 400 bytes in 1 blocks значит: 400 байт потеряны точно. Выше valgrind показывает, в какой строке программы эта память выделена. Если утечек нет, он пишет All heap blocks were freed -- no leaks are possible.

Дальше: «Двойной free и память после free»

Санитайзер адресовключ при сборке

Работает в gcc и clang:

Bash
gcc -g -fsanitize=address prog.c -o prog
./prog

При утечках программа в конце работы выводит ERROR: LeakSanitizer: detected memory leaks и для каждого блока — размер и строку, где он выделен. Санитайзер заодно ловит выход за границы массива и работу с памятью после free.

Двойной free и память после free

Обратная ошибка — освободить память дважды или пользоваться ею после free:

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof(int));
    if (p == NULL) return 1;
    *p = 5;
    free(p);
    p = NULL;
    free(p);
    printf("Готово\n");
    return 0;
}
Вывод
Готово

После free(p) указателю присвоен NULL, а free(NULL) по стандарту ничего не делает — второй free безопасен. Без строки p = NULL; второй free — неопределённое поведение: обычно программа аварийно завершается с сообщением вроде free(): double free detected.

Обращение к памяти после free иногда «работает» — память ещё не отдана кому-то другому. Это не значит, что ошибки нет: в другой раз программа упадёт или тихо испортит данные. Санитайзер находит такие места сразу.