Динамическое выделение памяти в C: мастерство malloc, calloc, realloc и free
Прежде чем погружаться в динамическое выделение памяти в C, важно понять, как управляется память в языке.
Как работает управление памятью в C
Когда вы объявляете переменную примитивного типа, компилятор автоматически выделяет под нее место в стеке. Например, float обычно занимает 4 байта (зависит от платформы). Вы можете подтвердить это с помощью оператора sizeof:
#include <stdio.h> int main() { float x; printf("The size of float is %d bytes", sizeof(x)); return 0; }
Вывод будет:
The size of float is 4 bytes
Массив заданного размера также выделяется непрерывно в стеке, каждый элемент занимает размер своего типа:
#include <stdio.h> int main() { float arr[10]; printf("The size of the float array with 10 elements is %d", sizeof(arr)); return 0; }
Результат:
The size of the float array with 10 elements is 40
Хотя выделение в стеке автоматично, многие программы требуют памяти, размер которой неизвестен до времени выполнения. Эта потребность решается динамическим выделением памяти, которое использует кучу вместо стека.
Динамическое выделение памяти в C
Динамическое выделение включает ручной запрос и освобождение памяти по мере необходимости, с указателями, отслеживающими выделенные блоки в куче. Стандартная библиотека C предоставляет несколько функций для этого:
- malloc() — выделяет непрерывный блок байтов и возвращает указатель на его первый байт.
- calloc() — выделяет массив элементов, инициализирует все байты нулями и возвращает указатель на первый элемент.
- realloc() — изменяет размер ранее выделенного блока, сохраняя его содержимое.
- free() — освобождает ранее выделенный блок обратно в кучу.
Рассмотрим каждую функцию подробно.
Функция malloc()
Функция malloc() означает «memory allocation». Она резервирует блок памяти заданного размера и возвращает указатель на него. Указатель обычно приводят к желаемому типу:
ptr = (cast_type *) malloc(byte_size);
Пример:
#include <stdlib.h> int main(){ int *ptr; ptr = malloc(15 * sizeof(*ptr)); /* 15 целых чисел */ if (ptr != NULL) { *(ptr + 5) = 480; /* шестой элемент */ printf("Value of the 6th integer is %d", *(ptr + 5)); } }
Вывод:
Value of the 6th integer is 480
Ключевые моменты:
- Используйте
sizeof(*ptr)для устойчивости кода к изменению типов. - Проверяйте возврат
NULLдля обнаружения неудачи выделения. - Выделенная память непрерывна; обращайтесь к ней как к массиву, используя арифметику указателей.
Динамическое выделение не ограничено примитивными типами; работает со структурами, символьными массивами и др.
Функция free()
Память, выделенная через malloc(), calloc() или realloc(), должна быть явно освобождена для предотвращения утечек. Функция free() выполняет эту задачу:
#include <stdio.h> int main() { int* ptr = malloc(10 * sizeof(*ptr)); if (ptr != NULL){ *(ptr + 2) = 50; printf("Value of the 2nd integer is %d", *(ptr + 2)); } free(ptr); }
Вывод:
Value of the 2nd integer is 50
Функция calloc()
calloc() выделяет несколько блоков одинакового размера и инициализирует каждый байт нулем, что идеально для массивов и структур, требующих обнуленной памяти:
ptr = (cast_type *) calloc(n, size);
Пример — суммирование арифметической последовательности:
#include <stdio.h> int main() { int i, *ptr, sum = 0; ptr = calloc(10, sizeof(int)); if (ptr == NULL) { printf("Error! memory not allocated."); exit(0); } for (i = 0; i < 10; ++i) { *(ptr + i) = i; sum += *(ptr + i); } printf("Sum = %d", sum); free(ptr); return 0; }
Результат:
Sum = 45
calloc() vs. malloc()
Ключевые различия:
- Инициализация:
calloc()обнуляет память;malloc()оставляет неинициализированной (мусор). - Использование:
calloc()предпочтительнее для массивов или структур, нуждающихся в нулевых значениях;malloc()проще, когда инициализация не требуется. - Производительность: Обе работают схоже, но
calloc()может быть чуть медленнее из-за обнуления.
Функция realloc()
Функция realloc() изменяет размер существующего выделения, сохраняя его содержимое:
ptr = realloc(ptr, newsize);
Пример — расширение строкового буфера:
#include <stdio.h> int main () { char *ptr = malloc(10); strcpy(ptr, "Programming"); ptr = realloc(ptr, 20); // увеличить strcat(ptr, " In 'C'"); printf("%s, Address = %u", ptr, ptr); free(ptr); return 0; }
Если перераспределение не удается, realloc() возвращает NULL, а исходный блок остается валидным.
Динамические массивы
Динамические массивы позволяют количеству элементов расти во время выполнения — частый паттерн в алгоритмах и структурах данных:
#include <stdio.h> int main() { int *arr_dynamic = NULL; int elements = 2, i; arr_dynamic = calloc(elements, sizeof(int)); for (i = 0; i < elements; i++) arr_dynamic[i] = i; for (i = 0; i < elements; i++) printf("arr_dynamic[%d]=%d\n", i, arr_dynamic[i]); elements = 4; arr_dynamic = realloc(arr_dynamic, elements * sizeof(int)); printf("After realloc\n"); for (i = 2; i < elements; i++) arr_dynamic[i] = i; for (i = 0; i < elements; i++) printf("arr_dynamic[%d]=%d\n", i, arr_dynamic[i]); free(arr_dynamic); }
Результат:
arr_dynamic[0]=0 arr_dynamic[1]=1 After realloc arr_dynamic[0]=0 arr_dynamic[1]=1 arr_dynamic[2]=2 arr_dynamic[3]=3
Резюме
- Динамическая память позволяет выделять и освобождать пространство в куче во время выполнения.
- Используйте
malloc()для одиночных блоков,calloc()для обнуленных массивов. - Всегда проверяйте
NULLдля обнаружения неудач выделения. - Освобождайте память с
free()для предотвращения утечек. - Изменяйте размер выделений с
realloc(), когда нужно больше места. - Динамические массивы — мощный паттерн для гибких структур данных.
Язык C
- Понимание ROM, PROM, EPROM и EEPROM: дизайн, программирование и идентификация
- Основы ввода-вывода в C#: Console.WriteLine, ReadLine и форматирование
- Освоение операторов using в C#: импорт, псевдонимы и статические директивы
- Управление памятью в C++: экспертное использование new и delete
- Четыре проверенных паттерна пользовательских функций в C
- Динамическое выделение памяти в C: освоение malloc, calloc, realloc и free
- Стандартные библиотечные функции в C: практическое руководство
- Динамическое выделение массивов в C++: практическое руководство с примерами кода
- Функции в C++: подробное объяснение с практическими примерами кода
- Функции в C: объявление, определение, вызов, аргументы (по значению и по ссылке), рекурсия