Помимо статической и стековой памяти, существует еще практически неограниченный ресурс памяти, которая называется динамическая, или куча (heap). Программа может захватывать участки динамической памяти нужного размера. После использования ранее захваченный участок динамической памяти следует освободить.
Под динамическую память отводится пространство виртуальной памяти процесса между статической памятью и стеком.
Структура динамической памяти автоматически поддерживается исполняющей системой языка С или C++. Динамическая память состоит из захваченных и свободных сегментов, каждому из которых предшествует описатель сегмента. При выполнении запроса на захват памяти исполняющая система производит поиск свободного сегмента достаточного размера и захватывает в нем отрезок требуемой длины. При освобождении сегмента памяти он помечается как свободный, при необходимости несколько подряд идущих свободных сегментов объединяются.
В языке С для захвата и освобождения динамической памяти применяются стандартные функции malloc и free, описания их прототипов содержатся в стандартном заголовочном файле "stdlib.h". (Имя malloc является сокращением от memory allocate - "захват памяти".) Прототипы этих функций выглядят следующим образом:
void *malloc(size_t n); // Захватить участок памяти
// размером в n байт
void free(void *p); // Освободить участок памяти с адресом p
Здесь n - это размер захватываемого участка в байтах, size_t - имя одного из целочисленных типов, определяющих максимальный размер захватываемого участка. Тип size_t задается в стандартном заголовочном файле "stdlib.h" с помощью оператора typedef. Это обеспечивает независимость текста С-программы от используемой архитектуры. В 32-разрядной архитектуре тип size_t определяется как беззнаковое целое число:
typedef unsigned int size_t;
Функция malloc возвращает адрес захваченного участка памяти или ноль в случае неудачи (когда нет свободного участка достаточно большого размера). Функция free освобождает участок памяти с заданным адресом. Для задания адреса используется указатель общего типа void *. После вызова функции malloc его необходимо привести к указателю на конкретный тип, используя операцию приведения типа. Например, в следующем примере захватывается участок динамической памяти размером в 4000байтов, его адрес присваивается указателю на массив из 1000 целых чисел:
int *a; // Указатель на массив целых чисел
. . .
a = (int *) malloc(1000 * sizeof(int));
…
free(a); // Освобождение выделенной памяти
Выражение в аргументе функции malloc равно 4000, поскольку размер целого числа sizeof(int) равен четырем байтам. Для преобразования указателя используется операция приведения типа (int *) от указателя обобщенного типа к указателю на целое число.
Операторы new и delete языка C++
В языке C++ для захвата и освобождения динамической памяти используются операторы new и delete. Они являются частью языка C++, в отличие от функций malloc и free, входящих в библиотеку стандартных функций С.
Пусть T - некоторый тип языка С или C++, p - указатель на объект типа T. Тогда для захвата памяти размером в один элемент типа T используется оператор new:
T *p;
p = new T;
Например, для захвата восьми байтов под вещественное число типа double используется фрагмент
double *p;
p = new double;
При использовании new, в отличие от malloc, не нужно приводить указатель от типа void* к нужному типу: оператор new возвращает указатель на тип, записанный после слова new. Сравните два эквивалентных фрагмента на С и C++:
double *p;
p = (double*) mallos(sizeof(double));
double *p;
p = new double;
Конечно, второй фрагмент гораздо короче и нагляднее.
Оператор new удобен еще и тем, что можно присвоить начальное значение объекту, созданному в динамической памяти (т.е. выполнить инициализацию объекта). Для этого начальное значение записывается в круглых скобках после имени типа, следующего за словом new. Например, в приведенной ниже строке захватывается память под вещественное число, которому присваивается начальное значение 1.5:
double *p = new double(1.5);
Этот фрагмент эквивалентен фрагменту
double *p = new double;
*p = 1.5;
С помощью оператора new можно захватывать память под массив элементов заданного типа. Для этого в квадратных скобках указывается длина захватываемого массива, которая может представляться любым целочисленным выражением. Например, в следующем фрагменте в динамической памяти создается массив размера m для хранения вещественных чисел:
double *a;
int m = 100;
a = new double[m];
Оператор delete освобождает память, захваченную ранее с помощью оператора new, например,
double *p = new double(1.5); // Захват и инициализация
. . .
delete p; // Освобождение памяти
Если память под массив была захвачена с помощью векторной формы оператора new, то для ее освобождения следует использовать векторную форму оператора delete, в которой после слова delete записываются пустые квадратные скобки:
double *a = new double[100]; // Захватываем массив
. . .
delete[] a; // Освобождаем массив.