Динамическая память: malloc, free и ошибки памяти

⏱ 25 мин · 🧩 заданий: 5

Адресное пространство процесса

Каждый процесс видит собственную виртуальную память. В Linux на x86-64 она устроена примерно так:

старшие адреса ┌──────────────────────────┐ │ ядро (недоступно) │ ├──────────────────────────┤ │ стек ↓ │ локальные переменные, кадры функций │ │ │ отображения mmap │ разделяемые библиотеки, mmap-память │ │ │ куча ↑ │ malloc; граница — brk ├──────────────────────────┤ │ .bss │ глобальные без инициализации (нули) │ .data │ глобальные с инициализацией │ .rodata │ константы, строковые литералы │ .text │ машинный код └──────────────────────────┘ младшие адреса

Посмотреть реальную карту можно в файле /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 — функция библиотеки, а не системный вызов. Она просит у ядра большие куски и сама раздаёт их по частям:

  1. brk/sbrk — сдвинуть верхнюю границу кучи. Подходит для мелких блоков.
  2. 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

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Написать код

Выделите в куче массив из n чисел double, проверьте результат на NULL (при ошибке — perror("malloc") и return 1;), а в конце освободите память.

Задание 2 Заполнить таблицу

Какая ошибка в каждом фрагменте?

Утечка памятиДвойное освобождениеИспользование после freeНет ошибки
p = malloc(10); p = malloc(20); free(p);
free(p); free(p);
free(p); p[0] = 1;
free(p); p = NULL; free(p);
Отметьте ячейку — «+», пустая — «−».
Задание 3 Один вариант ответа

Чем calloc(n, size) отличается от malloc(n * size)?

Задание 4 Несколько вариантов

Как библиотека malloc получает память от ядра в Linux?

Задание 5 Один вариант ответа

Ядро выделило процессу 1 ГиБ через mmap, но программа ещё ни разу не записала в эту память. Сколько физической памяти реально занято?

Войдите, чтобы отмечать пройденные уроки.