Как работает malloc
Куча и списки свободной памяти.
«Как работает malloc» — бесплатный урок C Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C Academy содержит 4 уроков всего.
Что на самом деле делает malloc
Когда вы вызываете malloc(n), библиотека C передаёт вам указатель как минимум на n доступных байт. Но куча — это всего лишь область памяти процесса, которой управляет распределитель памяти.
Задача распределителя — вести учёт: отслеживать используемые и свободные байты и эффективно повторно использовать освобождённую память.
Куча поступает из OS
Распределитель не создаёт память из ничего. Он запрашивает у операционной системы большие блоки с помощью системных вызовов вроде brk/sbrk или mmap.
Затем он разделяет эти блоки на меньшие части для ваших вызовов malloc. Обращение к OS обходится дорого, поэтому распределители запрашивают память большими объёмами и повторно используют её.
/* Conceptual: grow the heap by 4096 bytes */
void *base = sbrk(4096);
if (base == (void *)-1) {
/* out of memory */
}sbrk и граница программы
sbrk(n) перемещает «границу программы» вверх на n байт и возвращает прежнее значение границы. Открытая таким образом область становится доступной памятью кучи.
Этот подход прост и линеен, но не позволяет легко вернуть память из середины области. Современные распределители предпочитают mmap для больших запросов.
void *prev_break = sbrk(0); /* current break */
sbrk(1024); /* grow by 1 KB */
/* prev_break now points to fresh memory */Метаданные блока
Для каждого выделения распределитель хранит рядом с данными небольшой заголовок: размер блока и информацию о том, свободен ли он. Благодаря этому заголовку free может работать только с переданным ему указателем на данные.
Указатель, который вы получаете от malloc, указывает после заголовка, поэтому метаданные остаются скрытыми.
typedef struct block {
size_t size;
int free;
struct block *next;
} block_t;Указатель сразу после заголовка
Распространённый приём — арифметика указателей: пользовательский указатель равен header + 1. Если дан пользовательский указатель, заголовок находится на один block_t перед ним.
Так free(p) восстанавливает размер выделенного блока, хотя вам не нужно передавать этот размер отдельно.
block_t *hdr = (block_t *)user_ptr - 1;
printf("block size = %zu\n", hdr->size);Демонстрация небольшой структуры заголовка
Разместим заголовок в статическом буфере и прочитаем его обратно. Это показывает, как настоящий распределитель разделяет область на заголовок и полезную нагрузку.
Вызовы OS не используются, поэтому программа работает где угодно.
#include <stdio.h>
#include <stddef.h>
typedef struct { size_t size; int free; } block_t;
static char buffer[256];
int main(void) {
block_t *h = (block_t *)buffer;
h->size = 64;
h->free = 0;
void *payload = (char *)buffer + sizeof(block_t);
printf("header bytes = %zu\n", sizeof(block_t));
printf("payload offset = %ld\n", (long)((char *)payload - buffer));
printf("size field = %zu\n", h->size);
return 0;
}Идея списка свободных блоков
Многие распределители объединяют свободные блоки в связный список. Когда вы вызываете malloc, распределитель просматривает этот список в поисках достаточно большого блока.
Когда вы вызываете free, блок помечается как свободный и возвращается в список для последующего повторного использования, что позволяет избежать нового запроса к OS.
block_t *find_free(block_t *head, size_t size) {
block_t *b = head;
while (b && !(b->free && b->size >= size))
b = b->next;
return b;
}Что должен делать free
free(p) находит заголовок для p, помечает блок как свободный и в идеале объединяет его с соседними свободными блоками (слияние), чтобы уменьшить фрагментацию.
Двойной вызов free для одного указателя или освобождение указателя, не относящегося к куче, приводит к неопределённому поведению, поскольку метаданные повреждаются.
void my_free(void *p) {
if (!p) return;
block_t *hdr = (block_t *)p - 1;
hdr->free = 1;
/* real allocators coalesce neighbors here */
}Фрагментация
Со временем освобождение и выделение блоков разного размера оставляет промежутки. Внешняя фрагментация означает, что свободная память есть, но она разбросана по слишком маленьким частям, неспособным удовлетворить запрос.
Внутренняя фрагментация — это неиспользуемое пространство внутри блока, который больше необходимого, часто из-за выравнивания или округления.
Требования к выравниванию
malloc должен возвращать память, выровненную для любого типа. В большинстве 64-разрядных систем это означает выравнивание по границе 16 байт, соответствующее требованиям max_align_t.
Невыровненные указатели могут привести к сбою на некоторых процессорах или замедлить доступ на других, поэтому распределители всегда округляют размер полезной нагрузки до границы выравнивания.
#include <stdalign.h>
/* alignof(max_align_t) is the strictest required alignment */
size_t a = alignof(max_align_t);Собираем всё вместе
Таким образом, минимальному распределителю нужны: источник памяти (статический буфер, sbrk или mmap), заголовки каждого блока, стратегия поиска свободного места и обработка выравнивания.
В следующих уроках мы создадим эти компоненты: сначала распределитель bump_alloc, затем списки свободных блоков, а после этого выравнивание и разделение блоков.
/* The four pillars of a custom allocator */
/* 1. memory source 2. block headers */
/* 3. free-block search 4. alignment */Быстрая проверка
Проверьте своё понимание внутреннего устройства распределителей памяти.
Итоги
malloc управляет кучей, полученной от OS через sbrk или mmap, и разделяет её на блоки со скрытыми заголовками, в которых хранятся размер и состояние свободен/занят.
Списки свободных блоков позволяют повторно использовать память, выравнивание подходит для любого типа, а фрагментация остаётся главной проблемой. Эти идеи лежат в основе распределителя, который мы создадим дальше.
Часто задаваемые вопросы
Урок «Как работает malloc» бесплатный?
Да — полный текст урока «Как работает malloc» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C Academy, подпишись на CoddyKit PRO. Курс C Academy содержит 4 уроков всего.
Чему я научусь в уроке «Как работает malloc»?
Куча и списки свободной памяти. Ты практикуешь C Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C Academy?
Предыдущий опыт не требуется. C Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Как работает malloc»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C Academy?
Да. Каждый урок C Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Как работает malloc
- Простой линейный распределитель
- Списки свободной памяти и повторное использование
- Выравнивание и разделение