У каждого массива, который вы писали до сих пор, размер был зашит на этапе компиляции: int scores[10]; резервирует десять ячеек, нужны вам три или три сотни. Это работает ровно до тех пор, пока размер не становится известен только во время выполнения — сколько строк в файле, сколько записей введёт пользователь. Динамическая память решает эту задачу: вы запрашиваете нужные байты тогда, когда они нужны, и возвращаете их, когда закончили.
Инструменты лежат в <stdlib.h>:
#include <stdlib.h>
Зачем нужна куча
C даёт программе две основные области памяти. Локальные переменные живут в автоматической памяти (в стеке): их размер определяет компилятор, и они исчезают в момент выхода из функции. Динамические выделения живут в куче: размер вы задаёте во время выполнения, и блок остаётся жив, пока вы явно его не освободите.
Именно второе свойство и есть настоящая причина использовать malloc. Функция может выделить блок, вернуть указатель, и память останется корректной у вызывающего — локальный массив так никогда не сможет. Подробное сравнение двух областей — в стек против кучи.
malloc: запрос байтов
malloc принимает количество байт и возвращает void *, указывающий на такое количество неинициализированной памяти, либо NULL, если запрос не удался.
Четыре момента в этой короткой программе и составляют всю дисциплину:
- Выражение размера —
n * sizeof *scores. Читайте его как «n штук того, на что указываетscores». - Результат проверяется на
NULLдо любого использования. - С блоком работают ровно как с массивом —
scores[i]работает, потому что индексация есть арифметика указателей. - Он освобождается один раз, и указателю затем присваивается
NULL.
Обратите внимание: приведения типа возвращаемого значения нет. В C void * автоматически преобразуется в любой указатель на объект, а запись (int *)malloc(...) добавляет шум и заодно маскирует отсутствующий #include <stdlib.h>. (C++ требует приведения; C — нет.)
Идиома с sizeof
Почему sizeof *scores, а не sizeof(int)? Потому что так размер не может устареть.
int *a = malloc(n * sizeof *a); /* n int-ов, каким бы int здесь ни был */
long *b = malloc(n * sizeof *b); /* та же форма строки, верный размер */
/* хрупкая форма */
long *c = malloc(n * sizeof(int)); /* компилируется, выделяет слишком мало */
Последняя строка — настоящий баг, о котором ни один компилятор не предупредит: c имеет тип long *, размер выделения посчитан для int, и любая запись за первой половиной уходит за границу. С sizeof *c размер следует за объявлением автоматически.
sizeof *p не разыменовывает p — sizeof вычисляется на этапе компиляции по одному лишь типу, поэтому это безопасно, даже когда p не инициализирован или равен NULL.
Неинициализированная память
malloc не очищает то, что вам выдаёт. В байтах лежит всё, что было там раньше.
Когда нужно, чтобы блок сразу был обнулён, это делает calloc за один шаг — см. calloc и realloc.
free: вернуть память обратно
free(p) возвращает блок аллокатору. Три правила:
- Передавайте ровно тот указатель, который вернул
malloc, а не сдвинутый.free(p + 1)— неопределённое поведение. - Освобождайте каждый блок ровно один раз. Дважды — это двойное освобождение, оно портит внутренний учёт аллокатора.
- После освобождения указатель становится «протухшим». Использовать его — это обращение после освобождения (use-after-free).
int *p = malloc(sizeof *p);
free(p);
*p = 5; /* use-after-free - неопределённое поведение */
free(p); /* двойное освобождение - неопределённое поведение */
Ни одна из этих строк не обязана уронить программу немедленно, и именно этим они опасны: программа может проработать несколько минут и сломаться в совершенно другом месте. Дешёвая защита — обнулить указатель:
free(p);
p = NULL;
free(NULL) по стандарту явно ничего не делает, поэтому повторный free(p) после этого безвреден, а *p превращается в мгновенное падение в месте настоящей ошибки вместо тихого повреждения памяти.
Динамический массив от начала до конца
Вот как выглядит реальное выделение: прочитать количество во время выполнения, взять под него блок, использовать, освободить.
Приведение количества к size_t начинает играть роль, когда размеры становятся большими: n * sizeof *values с обычным int n может переполниться ещё до того, как результат умножения дойдёт до malloc, и выдаст блок заметно меньше задуманного. Умножение в size_t этого избегает.
Выделение внутри функции
Именно правило времени жизни в куче делает такой код законным — и делает незаконным возврат локального массива:
len + 1 оставляет место под нулевой терминатор — то же правило расчёта размера, что и везде в строках. И обратите внимание на комментарий над функцией: когда функция возвращает выделенную память, вопрос «кто её освобождает» — часть её контракта. Записать это не бюрократия; это единственное, что предотвращает утечку.
Структуры в куче
Та же идиома работает для любого типа:
Обратите внимание: вторая ветка обработки ошибки освобождает p перед возвратом. Как только функция держит больше одного выделения, каждый путь с ошибкой обязан отпустить уже занятое — это самый частый источник утечек, описанных в утечках памяти.
Чек-лист
- Всегда проверяйте результат
mallocнаNULLдо первого использования. - Считайте размер как
n * sizeof *p, умножая вsize_t. - В C приведение возвращаемого значения не нужно.
- Память из
mallocне инициализирована; пишите до того, как читать. - Освобождайте ровно один раз, исходным указателем, затем присваивайте ему
NULL. - Каждый путь с ошибкой после выделения обязан освободить то, что уже занято.
Часто задаваемые вопросы
Что делает malloc в C?
malloc(n) запрашивает n байт памяти из кучи и возвращает указатель на начало этого блока либо NULL, если запрос не удалось удовлетворить. Блок живёт, пока вы не передадите тот же самый указатель в free — в отличие от локальной переменной он не освобождается при выходе из функции.
Как пользоваться malloc и free в C?
Выделите память через int *p = malloc(n * sizeof *p);, проверьте if (p == NULL) до первого обращения, работайте с блоком как с массивом, а затем ровно один раз вызовите free(p);, когда закончите. Присваивание p = NULL после этого превращает случайное позднее обращение в честное падение вместо тихого повреждения памяти.
Почему malloc(n * sizeof *p) лучше, чем malloc(n * sizeof(int))?
Потому что sizeof *p автоматически следует за типом указателя. Если p позже станет long * или struct Point *, размер выделения обновится сам; а записанный вручную sizeof(int) оставит размер, который стал неверным и о котором компилятор не предупредит.
Что будет, если не освобождать память в C?
Блок останется выделенным до конца жизни процесса — это утечка памяти. Короткой программе это сходит с рук, потому что операционная система всё возвращает при выходе, но долгоживущая программа теряет понемногу на каждом проходе и в конце концов исчерпает память. См. утечки памяти.