При создании массива с фиксированными размерами под него выделяется определенная память. Например, пусть у нас будет массив с пятью элементами:
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);
}