Динамическая память: malloc, free и ошибки памяти
Адресное пространство процесса
Каждый процесс видит собственную виртуальную память. В Linux на x86-64 она устроена примерно так:
Посмотреть реальную карту можно в файле /proc/<pid>/maps (cat /proc/self/maps).
malloc и free
#include <stdlib.h>
int *a = malloc(100 * sizeof(int)); /* 100 int в куче; содержимое — мусор */
if (a == NULL) { perror("malloc"); exit(1); }
int *z = calloc(100, sizeof(int)); /* 100 int, обнулены */
a = realloc(a, 200 * sizeof(int)); /* изменить размер (может переехать!) */
free(a);
free(z);
| Функция | Что делает |
|---|---|
malloc(size) |
выделить size байт |
calloc(n, size) |
выделить n*size байт и обнулить |
realloc(p, size) |
изменить размер блока, возможно переместив его |
free(p) |
вернуть блок; free(NULL) безопасен |
Правило
На каждый malloc — ровно один free. Сразу после free обнуляйте указатель: free(p); p = NULL;.
Откуда malloc берёт память
malloc — функция библиотеки, а не системный вызов. Она просит у ядра большие куски и сама раздаёт их по частям:
brk/sbrk— сдвинуть верхнюю границу кучи. Подходит для мелких блоков.mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)— получить новый участок адресного пространства. glibc использует это для больших блоков.
Ядро при этом не выделяет физическую память сразу — только отмечает диапазон адресов как допустимый. Физический кадр появится при первом обращении к странице: процессор не найдёт отображения, произойдёт страничный отказ (page fault), и ядро выделит кадр.
strace -e trace=brk,mmap ./program # увидеть, как malloc обращается к ядру
Типичные ошибки
/* 1. Утечка: потеряли единственный указатель на блок */
char *p = malloc(100);
p = malloc(200); /* первый блок больше не освободить */
/* 2. Двойное освобождение — порча структур malloc */
free(p);
free(p);
/* 3. Использование после free (висячий указатель) */
free(p);
p[0] = 'x';
/* 4. Выход за границы блока */
int *a = malloc(10 * sizeof(int));
a[10] = 1;
/* 5. Забыли sizeof */
int *b = malloc(10); /* 10 байт, а не 10 int! */
Такие ошибки находят инструменты:
gcc -g -fsanitize=address prog.c -o prog && ./prog # AddressSanitizer
valgrind --leak-check=full ./prog
Что будет с памятью после завершения процесса?
Вся память процесса — куча, стек, отображения — освобождается ядром при завершении, даже если free не вызывался. Утечки опасны для долгоживущих программ: серверов, демонов, самого ядра. Но аккуратное освобождение — признак правильной программы, и на экзамене за его отсутствие могут снять баллы.
Итог
- Адресное пространство: код, данные, куча (растёт вверх), mmap-область, стек (растёт вниз).
malloc/calloc/realloc/free; проверка наNULL; на каждыйmalloc— одинfree.- malloc получает память у ядра через
brkиmmap; физические страницы выделяются лениво — при первом обращении. - Утечки, двойное освобождение, висячие указатели ловят ASan и valgrind.
Практика решено 0 из 5
Выделите в куче массив из n чисел double, проверьте результат на NULL
(при ошибке — perror("malloc") и return 1;), а в конце освободите память.
double *arr = malloc(n * sizeof(double));
if (arr == NULL) {
perror("malloc");
return 1;
}
/* ... работа с arr ... */
free(arr);
Какая ошибка в каждом фрагменте?
| Утечка памяти | Двойное освобождение | Использование после free | Нет ошибки | |
|---|---|---|---|---|
| p = malloc(10); p = malloc(20); free(p); | ||||
| free(p); free(p); | ||||
| free(p); p[0] = 1; | ||||
| free(p); p = NULL; free(p); |
Чем calloc(n, size) отличается от malloc(n * size)?
Как библиотека malloc получает память от ядра в Linux?
Ядро выделило процессу 1 ГиБ через mmap, но программа ещё ни разу не записала в эту память. Сколько физической памяти реально занято?
Войдите, чтобы отмечать пройденные уроки.