Утечка памяти: как найти и исправить
Утечка памяти — память, выделенная через malloc, calloc или realloc, больше не нужна, но не освобождена через free. Программа не падает, просто занимает всё больше памяти. Для короткой учебной программы это почти незаметно — после её завершения система забирает всю память. Но в программе, которая работает долго или выделяет память в цикле, утечка однажды съедает всю память.
Правило одно: на каждый успешный malloc (calloc, realloc) — ровно один free, когда память больше не нужна.
Как появляются утечки
Забытый free в цикле или функции.
#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 байт
}Потерянный указатель. В указатель записали новый адрес, а старый блок не освободили:
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:
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.
Санитайзер адресовключ при сборке
Работает в gcc и clang:
gcc -g -fsanitize=address prog.c -o prog
./progПри утечках программа в конце работы выводит ERROR: LeakSanitizer: detected memory leaks и для каждого блока — размер и строку, где он выделен. Санитайзер заодно ловит выход за границы массива и работу с памятью после free.
Двойной free и память после free
Обратная ошибка — освободить память дважды или пользоваться ею после free:
#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 иногда «работает» — память ещё не отдана кому-то другому. Это не значит, что ошибки нет: в другой раз программа упадёт или тихо испортит данные. Санитайзер находит такие места сразу.