Динамическая память
Зачем нужна динамическая память
Статический массив требует, чтобы размер был известен при компиляции:
int a[100]; /* максимум 100 элементов, больше не поместится */
Но часто размер становится известен только во время работы программы: пользователь вводит
N, файл содержит неизвестное число записей, список растёт по мере добавления элементов.
Если зарезервировать «массив побольше», память может закончиться или её будет жалко.
Динамическая память (куча, heap) — область, в которой программа сама запрашивает и освобождает память во время выполнения. Размер задаётся в байтах, а не в элементах.
Аналогия. Статический массив — заранее купленный шкаф фиксированного размера. Динамическая память — склад, где можно попросить ровно столько полок, сколько нужно сейчас, а потом вернуть их.
malloc: выделить память
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int n;
if (scanf("%d", &n) != 1 || n <= 0)
{
printf("Ошибка: нужно положительное N\n");
return 1;
}
int *a = malloc((size_t)n * sizeof(*a));
if (a == NULL)
{
printf("Не хватило памяти\n");
return 1;
}
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
}
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
free(a);
a = NULL;
return 0;
}
Разберём строку выделения:
sizeof(*a)— размер одного элемента, на который смотритa. Если тип элемента когда-нибудь изменится, код останется правильным;n * sizeof(*a)— общий размер в байтах;(size_t)— приведение к беззнаковому типу размера, чтобы произведение не переполнилось при большихn;mallocвозвращаетvoid *; в C приводить результат кint *не нужно;- результат обязательно проверяют на
NULL.
malloc не заполняет память: там могут быть любые байты. Читать элемент до записи нельзя.
calloc: выделить и обнулить
calloc принимает количество элементов и размер одного элемента, а память заполняет нулями:
int *a = calloc((size_t)n, sizeof(*a));
if (a == NULL)
{
return 1;
}
Это удобно для массивов счётчиков, матриц и структур, которые должны начинаться с нулей.
Для обычного массива, который сразу заполняется, разницы с malloc почти нет.
realloc: изменить размер
realloc меняет размер ранее выделенного блока. Старое содержимое сохраняется в пределах
нового размера:
int *bigger = realloc(a, (size_t)(n + 1) * sizeof(*a));
if (bigger == NULL)
{
/* Старый блок a остался жив — его нужно освободить. */
free(a);
return 1;
}
a = bigger;
a[n] = 42;
n++;
Результат всегда сохраняют во временную переменную. Если записать a = realloc(a, ...)
и realloc вернёт NULL, старый указатель будет потерян — получится утечка.
realloc(NULL, size) работает как malloc(size). realloc(ptr, 0) — особый случай; в учебных
задачах его лучше не использовать, а просто вызвать free.
free: вернуть память
free(a);
a = NULL;
freeпринимает только указатель, который вернулmalloc,callocилиrealloc;free(NULL)безопасен и ничего не делает;- повторный
freeтого же блока — ошибка (double free); - после
freeпамять больше не принадлежит программе: обращаться к ней нельзя; - присваивание
a = NULLпослеfree— полезная привычка, но она не заменяет аккуратность: если на блок есть другие указатели, они останутся «висячими».
Утечки и висячие указатели
Утечка памяти (memory leak) — блок выделен, но указатель на него потерян, и free
вызвать уже невозможно:
int *p = malloc(sizeof(*p));
*p = 12;
p = malloc(sizeof(*p)); /* старый блок потерян — утечка */
*p = 0;
free(p);
Висячий указатель (dangling pointer) — указатель на уже освобождённую память:
int *p = malloc(sizeof(*p));
free(p);
*p = 10; /* ошибка: память уже вернули */
В маленькой учебной программе утечка может быть незаметна, потому что память освобождается при завершении процесса. Но рассчитывать на это нельзя: в большой программе или в цикле утечки быстро съедают память. В лабораторной работе память нужно освобождать явно.
Динамический массив: типовой каркас
#include <stdio.h>
#include <stdlib.h>
int *input_array(int n);
void print_array(const int *a, int n);
void free_array(int *a);
int main(void)
{
int n;
if (scanf("%d", &n) != 1 || n <= 0)
{
printf("Ошибка ввода\n");
return 1;
}
int *a = input_array(n);
if (a == NULL)
{
printf("Не хватило памяти\n");
return 1;
}
print_array(a, n);
free_array(a);
return 0;
}
int *input_array(int n)
{
int *a = malloc((size_t)n * sizeof(*a));
if (a == NULL)
{
return NULL;
}
for (int i = 0; i < n; i++)
{
if (scanf("%d", &a[i]) != 1)
{
free(a);
return NULL;
}
}
return a;
}
void print_array(const int *a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
void free_array(int *a)
{
free(a);
}
Функция input_array возвращает указатель на новый блок или NULL при ошибке. Владелец
блока — main: именно он вызывает free_array. Такой подход помогает не запутаться,
кто должен освобождать память.
Сколько памяти нужно
Размер вычисляется как количество элементов * sizeof(элемент). Для массива структур:
struct Student *group = malloc((size_t)n * sizeof(*group));
Для матрицы n × m из int:
int *matrix = malloc((size_t)n * m * sizeof(*matrix));
Важно использовать sizeof(*указатель), а не sizeof(указатель): во втором случае
получится размер самого указателя (обычно 8 байт), а не элемента.
Частые ошибки
| Ошибка | Что не так |
|---|---|
не проверили malloc на NULL | обращение по пустому адресу, падение |
malloc(sizeof(int *)) вместо sizeof(int) | выделен размер указателя, а не числа |
malloc(n) вместо malloc(n * sizeof(*a)) | выделено n байт вместо n элементов |
a = realloc(a, ...) без временной переменной | при ошибке старый блок потерян |
забыли free | утечка памяти |
free дважды | повреждение кучи, неопределённое поведение |
обратились к памяти после free | висячий указатель, непредсказуемый результат |
читают элемент до записи после malloc | в памяти лежит «мусор» |
Документация
Что запомнить
- Динамическая память выделяется во время работы программы и освобождается вручную.
mallocвыделяет блок в байтах,calloc— и обнуляет его,realloc— меняет размер.- Результат выделения всегда проверяют на
NULL. freeвызывают ровно один раз для каждого выделенного блока.- После
freeпамятью пользоваться нельзя. - Размер считают как
количество * sizeof(*указатель).