Конспект по С++: динамическое распределение памяти

Суббота , 6, Июль 2019 Leave a comment

Динамическое распределение памяти – способ выделения оперативной памяти под объекты (переменную, массив, структуру, класс и др.) в программе, когда выделение памяти под объект осуществляется во время выполнения программы.

При динамическом распределении памяти объекты размещаются в т.н. «куче» (англ. «heap»). Для размещения объекта в «куче» необходимо выделить в «куче» область памяти, размер которой соответствовал бы размеру размещаемого в «куче» объекта. Если область памяти требуемых размеров была успешно выделена, то эта выделенная под объект область памяти «изымается» из «кучи» и становится недоступной для последующих операций динамического выделения памяти вплоть до момента освобождения выделенной области памяти.

В языке C++ динамическое распределение памяти осуществляется при помощи оператора new: при успешном выделении памяти под объект оператор new возвращает адрес выделенной области памяти. Освобождение выделенной под объект области памяти (выделенной в «куче» оператором new) осуществляется оператором delete. Логика динамического выделения памяти под объект оператором new и её освобождение оператором delete примерно следующая:

SomeObject* ptrObject = new SomeObject;
… /* какие-то действия с *ptrObject */
delete ptrObject;

Внимание! Каждому оператору new должен соответствовать свой оператор delete, то есть динамически выделенную в «куче» память под объект надо затем освободить. Если забыть об этом, то в «куче» скоро может не остаться свободной памяти для её динамического распределения, возникнет так называемая «утечка» памяти.

Синтаксис оператора new следующий:

typename* typePointer;

typePointer = new typename;

где typename – это любой фундаментальный тип данных (char, int, long, float и пр.) или объект, определённый пользователем, включая классы и структуры. Если typename – это тип класса, то он должен иметь доступный конструктор по умолчанию, который будет вызван для создания объекта.

Согласно стандарту C++, если запрос на выделение памяти оператором new удовлетворить невозможно, то генерируется исключение типа bad_alloc: если программа не перехватывает его, она будет досрочно завершена. Чтобы получить доступ к исключению типа bad_alloc, нужно включить в программу заголовочный файл <new>:

#include <new>

С помощью функции set_new_handler( <указатель> ), объявленной в заголовочном файле <new>, можно создать собственный обработчик исключительной ситуации, связанной с неудачным выделением памяти оператором new. Для этого в программе необходимо создать функцию обработки таких ситуаций и до первой попытки выделить память оператором new вызвать функцию set_new_handler( <указатель> ), передав ей в качестве параметра имя такой функции. Новая функция-обработчик исключения типа bad_alloc, адрес которой будет передан в качестве аргумента при вызове функции set_new_handler, должна принимать аргумент типа size_t и возвращать значение типа int. Например:

#include <new>

int OutOfMemory(size_t s_t)
{
 …
 cout << «Not enough memory! » << endl;
 return(0);
}

set_new_handler( OutOfMemory );

Таким образом, при неудачной попытке выделить память оператором new вызывается функция-обработчик OutOfMemory, которая, например, пытается освободить некоторую часть памяти в «куче». Затем оператор new снова попытается выделить необходимый объем памяти. При успешном выполнении этих действий обработка исключительной ситуации, возникшей из-за нехватки памяти, завершается. В противном случае описанный процесс будет повторен. Он будет повторяться бесконечное количество раз до тех пор, пока не будет выделен достаточный объем памяти или функция OutOfMemory сама не прекратит этот процесс. Прекратить этот процесс с помощью функции OutOfMemory можно несколькими способами:

  • вызвать появление исключительной ситуации (например, std::bad_alloc() );
  • установить другой обработчик исключительной ситуации, который позволит выделить больше памяти;
  • передать значение NULL для set_new_handler( <указатель> );
  • вообще завершить выполнение программы.

Кроме того, если функция-обработчик исключения типа bad_alloc, установленная последним вызовом функции set_new_handler( <указатель> ), возвращает нулевое значение, то оператор new прекратит попытки выделить память и возвратит в программу указатель NULL. Символьная константа NULL обычно объявлена как:

#define NULL ((void*)0)

В таком случае результат выполнения запроса на выделение памяти оператором new выясняется проверкой значения, возвращаемого оператором new, например:

SomeObject* ptrObject;

ptrObject = new SomeObject;
if( ptrObject == NULL ) cout << «Not enough memory!» << endl;

При создании нового обработчика такая же цепь событий произойдёт и для нового обработчика, который будет вызван при следующей неудачной попытке выделить память оператором new.

Если в качестве адреса обработчика функции set_new_handler( <указатель> ) передать значение NULL, то обработка выполняться не будет, а будет вызвана исключительная ситуация std::bad_alloc.

В книге Герберта Шилдта «C++. Базовый курс.» описана альтернативная форма оператора new:

typename* typePointer;

typePointer = new(nothrow) typename;

При неудачной попытке выделения памяти с использованием альтернативной формы оператора newnew(nothrow), вместо генерирования исключения оператор new(nothrow) возвращает значение NULL.

Правила работы с указателями и динамическим распределением памяти:

  • лучше инициализировать указатель значением NULL если он не используется сразу и больше инициализировать его нечем;
  • лучше сразу установить значение указателя в NULL после уничтожения объекта, на который он указывал;
  • можно без опасений применять оператор delete к указателю, установленному в NULL;
  • необходимо (!) следить за тем, чтобы не применять оператор delete к указателю дважды (то есть когда объект, на который он указывал, уже не существует, а значение указателя не NULL).

Если при динамическом создании объекта в памяти используется ссылка, а не указатель, то синтаксис операторов new и delete меняется:

typename& typeRef = *new typename;

delete &typeRef;

То есть вместе со ссылкой необходимо использовать оператор взятия адреса. Кроме того, нельзя забывать, что ссылку нельзя установить в NULL, поэтому надо следить, чтобы не применить оператор delete дважды.

См. также:

Конспект по С++: классы и объектно-ориентированное программирование, объявление класса, спецификаторы доступа, конструкторы, списки инициализаторов

©Задорожный Сергей Михайлович, 2019г.