При создании массива с фиксированными размерами под него выделяется определенная память. Например, пусть у нас будет массив с пятью элементами:
double numbers[5] = {1.0, 2.0, 3.0, 4.0, 5.0};
Для такого массива выделяется память 5 * 8 (размер типа double) = 40 байт. Таким образом, мы точно знаем, сколько в массиве элементов и сколько он занимает памяти. Однако это не всегда удобно. Иногда бывает необходимо, чтобы количество элементов и соответственно размер выделяемой памяти для массива определялись динамически в зависимости от некоторых условий. Например, пользователь сам может вводить размер массива. И в этом случае для создания массива мы можем использовать динамическое выделение памяти.
Для управления динамическим выделением памяти используется ряд функций, которые определены в заголовочном файле stdlib.h:
malloc(). Имеет прототип
void *malloc(unsigned s);
Выделяет память длиной в s байт и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL
calloc(). Имеет прототип
void *calloc(unsigned n, unsigned m);
Выделяет память для n элементов по m байт каждый и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL
realloc(). Имеет прототип
void *realloc(void *bl, unsigned ns);
Изменяет размер ранее выделенного блока памяти, на начало которого указывает указатель bl, до размера в ns байт. Если указатель bl имеет значение NULL, то есть память не выделялась, то действие функции аналогично действию malloc
free(). Имеет прототип
void *free(void *bl);
Освобождает ранее выделенный блок памяти, на начало которого указывает указатель bl.
Функция malloc() выделяет память длиной для определенного количества байт и возвращает указатель на начало выделенной памяти. Через полученный указатель мы можем помещать данные в выделенную память. Рассмотрим простой пример:
#include <stdio.h> #include <stdlib.h> // для подключения функции malloc int main(void) { int *ptr = malloc(sizeof(int)); // выделяем память для одного int *ptr = 24; // помещаем значение в выделенную память printf("%d \n", *ptr); free(ptr); }
Здесь с помощью функции malloc
выделяется память для одного объекта int
. Чтобы узнать, сколько байтов надо выделить, передаем в функцию malloc
размер типа int на текущей и в результате получаем указатель ptr
, который указывает на выделенную память
int *ptr = malloc(sizeof(int));
То есть поскольку int на большинстве архитектур занимает 4 байта, то в большинстве случаев будет выделяться память объемом в 4 байта. Стоит отметить, что мы также могли бы получить размер через разыменование указателя:
int *ptr = malloc(sizeof *ptr);
Для универсальности возвращаемого значения в качестве результата функция malloc()
(как и calloc()
и realloc()
) возвращает указатель типа
void *. Но в нашем случае создается массив типа int, для управления которым используется указатель типа int *,
поэтому выполняется неявное приведение результата функции malloc к типу int *.
Далее через этот указатель с помощью операции разыменования помещаем в выделенный участок памяти число 24:
*ptr = 24;
В дальнейшем, используя указатель, можно получить значение из выделенного участка памяти:
printf("%d \n", *ptr);
После завершения работы освобождаем память, передавая указатель в функцию free()
:
free(ptr);
Стоит отметить, что теоретически мы можем столкнуться с тем, что функции malloc()
не удастся выделить требуемую память, и тогда она возвратит NULL. Чтобы избежать подобной
ситуации перед использованием указателя мы можем проверять его на значение NULL:
#include <stdio.h> #include <stdlib.h> // для подключения функции malloc int main(void) { int *ptr = malloc(sizeof(int)); // выделяем память для одного int if(ptr != NULL) { *ptr = 24; // помещаем значение в выделенную память printf("%d \n", *ptr); } free(ptr); }
Немотря на освобождение памяти с помощью функции free()
указатель сохраняет свой адрес, и теоретически мы можем обращаться к памяти по данному указателю. Однако
полученные значения уже будут неопределенными и недетеминированными. Поэтому некоторые советуют после освобождения памяти также устанавливать для указателя значение NULL
:
free(ptr); ptr = NULL;
Подобным образом можно выделять память и под набор объектов. Например, выделим память для массива из 4-х чисел int:
#include <stdio.h> #include <stdlib.h> int main(void) { int n = 4; int *ptr = malloc(n * sizeof(int)); // выделяем память для 4-х чисел int if(ptr) { // помещаем значения в выделенную память ptr[0] = 1; ptr[1] = 2; ptr[2] = 3; ptr[3] = 5; // получаем значения for(int i = 0; i < n; i++) { printf("%d", ptr[i]); } } free(ptr); }
Аналогичным образом можно выделять память для одной или набора структур:
#include <stdio.h> #include <stdlib.h> struct person { char* name; int age; }; int main(void) { // выделяем память для одной структуры person struct person *ptr = malloc(sizeof(struct person)); if(ptr) { // помещаем значения в выделенную память ptr->name = "Tom"; ptr->age = 38; // получаем значения printf("%s : %d", ptr->name, ptr->age); // Tom : 38 } free(ptr); return 0; }
Функция calloc() имеет прототип
void *calloc(unsigned n, unsigned m);
Она выделяет память для n элементов по m байт каждый и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL
В отличие от функции malloc()
она инициализирует все выделенные байты памяти нулями. Например, выделим память для одного объекта int
:
#include <stdio.h> #include <stdlib.h> int main(void) { // выделяем память для одного объекта int int *ptr = calloc(1, sizeof(int)); if(ptr) { // получаем значение по умолчанию - 0 printf("Initial value: %d", *ptr); // Initial value: 0 // устанавливаем новое значение *ptr = 15; // получаем новое значение printf("New value: %d", *ptr); // New value: 15 } free(ptr); return 0; }
Консольный вывод:
Initial value: 0 New value: 15
Подобным образом можно выделить память и для других объектов. Например, выделим память для массива из 4-х объектов int
:
#include <stdio.h> #include <stdlib.h> int main(void) { // выделяем память для 4-х объектов int int n = 4; int *ptr = calloc(n, sizeof(int)); if(ptr) { // устанавливаем значения ptr[0] = 1; ptr[1] = 2; ptr[2] = 3; ptr[3] = 5; // получаем значения for(int i = 0; i < n; i++) { printf("%d", ptr[i]); } } free(ptr); }
Функция realloc() позволяет изменить размер памяти, ранее выделенной с помощью функций malloc()
b calloc()
. Имеет прототип
void *realloc(void *bl, unsigned ns);
Первый параметр представляет указатель на ранее выделенный блок памяти. А второй параметр представляет новый размер блока памяти в байтах.
Если указатель bl имеет значение NULL, то есть память не выделялась, то действие функции аналогично действию malloc
Рассмотрим небольшой пример:
#include <stdio.h> #include <stdlib.h> int main(void) { // выделяем память для 1-го объекта int int size = sizeof(int); int *ptr = malloc(size); if(ptr) { // отображаем адрес и размер памяти printf("Addresss: %p \t Size: %d\n", (void*)ptr, size); } // расширяем память до размера 4-х объектов int size = 4 * sizeof(int); int *ptr_new = realloc(ptr, size); // если выделение памяти прошло успещно if(ptr_new) { printf("Reallocation\n"); // заново отображаем адрес и размер памяти printf("Addresss: %p \t Size: %d\n", (void*)ptr_new, size); free(ptr_new); // освобождаем новый указатель } else { free(ptr); // освобождаем старый указатель } }
Здесь сначала выделяем память для одного объекта int
с помощью функции malloc.
int size = sizeof(int); int *ptr = malloc(size);
Если память успешно выделена, то выводим на консоль адрес и размер выделенного блока памяти. Затем с помощью функции realloc
расширяем память до 4 объектов int
size = 4 * sizeof(int); int *ptr_new = realloc(ptr, size);
Если увеличение памяти прошло успешно, то заново выводим данные на консоль и освобождаем память по новому указателю. Если увеличение памяти прошло не удачно, то освобождаем память но старому указателю.
Консольный вывод в моем случае
Addresss: 0000018B078A82F0 Allocated: 4 Reallocation Addresss: 0000018B078A82F0 Allocated: 16
Стоит отметить, что нам необязательно создавать новый указатель, мы можем присвоить значение старому указателю:
#include <stdio.h> #include <stdlib.h> int main(void) { int size = sizeof(int); int *ptr = malloc(size); if(ptr) { printf("Addresss: %p \t Allocated: %d\n", (void*)ptr, size); } size = 4 * sizeof(int); ptr = realloc(ptr, size); // используем старый указатель if(ptr) { printf("Reallocation\n"); printf("Addresss: %p \t Allocated: %d\n", (void*)ptr, size); } free(ptr); }