Динамическое распределение памяти с использованием операторов new и delete
Для С++-программы существует два основных способа хранения информации в основной памяти компьютера. Первый состоит в использовании переменных. Область памяти, предоставляемая переменным, закрепляется за ними во время компиляции и не может быть изменена при выполнении программы. Второй способ заключается в использовании C++-системы динамического распределения памяти. В этом случае память для данных выделяется по мере необходимости из раздела свободной памяти, который расположен между вашей программой (и ее постоянной областью хранения) и стеком. Этот раздел называется «кучей» (heap). (Расположение программы в памяти схематично показано на рис. 9.2.)
Система динамического распределения памяти — это средство получения программой некоторой области памяти во время ее выполнения.

Динамическое выделение памяти — это получение программой памяти во время ее выполнения. Другими словами, благодаря этой системе программа может создавать переменные во время выполнения, причем в нужном (в зависимости от ситуации) количестве. Эта система динамического распределения памяти особенно ценна для таких структур данных, как связные списки и двоичные деревья, которые изменяют свой размер по мере их использования. Динамическое выделение памяти для тех или иных целей — важная составляющая почти всех реальных программ.
Чтобы удовлетворить запрос на динамическое выделение памяти, используется так называемая «куча». Нетрудно предположить, что в некоторых чрезвычайных ситуациях свободная память «кучи» может исчерпаться. Следовательно, несмотря на то, что динамическое распределение памяти (по сравнению с фиксированным) обеспечивает большую гибкость, но и в этом случае оно имеет свои пределы.
Оператор new позволяет динамически выделить область памяти.
Язык C++ содержит два оператора, new и delete, которые выполняют функции по выделению и освобождению памяти. Приводим их общий формат.
переменная-указатель = new тип_переменной;
Оператор delete освобождает ранее выделенную динамическую память.
Здесь элемент переменная-указатель представляет собой указатель на значение, тип которого задан элементом тип_переменной. Оператор new выделяет область памяти, достаточную для хранения значения заданного типа, и возвращает указатель на эту область памяти. С помощью оператора new можно выделить память для значений любого допустимого типа. Оператор delete освобождает область памяти, адресуемую заданным указателем. После освобождения эта память может быть снова выделена в других целях при последующем new-запросе на выделение памяти.
Поскольку объем «кучи» конечен, она может когда-нибудь исчерпаться. Если для удовлетворения очередного запроса на выделение памяти не существует достаточно свободной памяти, оператор new потерпит фиаско, и будет сгенерировано исключение. Исключение— это ошибка специального типа, которая возникает во время выполнения программы. В общем случае ваша программа должна обработать подобное исключение и по возможности выполнить действие, соответствующее конкретной ситуации. Если это исключение не будет обработано вашей программой, ее выполнение будет прекращено.
Такое поведение оператора new в случае невозможности удовлетворить запрос на выделение памяти определено стандартом C++. На такую реализацию настроены также все современные компиляторы, включая последние версии Visual C++ и C++ Builder. Однако дело в том, что некоторые более ранние компиляторы обрабатывают new-инструкции по-другому. Сразу после изобретения языка C++ оператор new при неудачном выполнении возвращал нулевой указатель. Позже его реализация была изменена так, чтобы в случае неудачи генерировалось исключение, как было описано выше.
Рассмотрим пример программы, которая иллюстрирует использование операторов new и delete.
using namespace std;
p = new int; // Выделяем память для int-значения.
*p = 20; // Помещаем в эту область памяти значение 20.
cout << *р; // Убеждаемся (путем вывода на экран) в работоспособности этого кода.
delete р; // Освобождаем память.
Эта программа присваивает указателю р адрес (взятой из «кучи») области памяти, которая будет иметь размер, достаточный для хранения целочисленного значения. Затем в эту область памяти помещается число 20, после чего на экране отображается ее содержимое. Наконец, динамически выделенная память освобождается.
Благодаря такому способу организации динамического выделения памяти оператор delete необходимо использовать только с тем указателем на память, который был возвращен в результате new-запроса на выделение памяти. Использование оператора delete с другим типом адреса может вызвать серьезные проблемы.
Инициализация динамически выделенной памяти
Используя оператор new, динамически выделяемую память можно инициализировать. Для этого после имени типа задайте начальное значение, заключив его в круглые скобки. Например, в следующей программе область памяти, адресуемая указателем p, инициализируется значением 99.
using namespace std;
p = new int (99); // Инициализируем память числом 99.
cout << *p; // На экран выводится число 99.
Выделение памяти для массивов
С помощью оператора new можно выделять память и для массивов. Вот как выглядит общий формат операции выделения памяти для одномерного массива:
переменная-указатель = new тип [размер];
Здесь элемент размер задает количество элементов в массиве.
Чтобы освободить память, выделенную для динамически созданного массива, используйте такой формат оператора delete:
Здесь элемент переменная-указатель представляет собой адрес, полученный при выделении памяти для массива (с помощью оператора new). Квадратные скобки означают для C++, что динамически созданный массив удаляется, а вся область памяти, выделенная для него, автоматически освобождается.
Важно! Более старые С++-компиляторы могут требовать задания размера удаляемого массива, поскольку в ранних версиях C++ для освобождения памяти, занимаемой удаляемым массивом, необходимо было применять такой формат оператора delete:
delete [размер] переменная-указатель;
Здесь элемент размер задает количество элементов в массиве. Стандарт C++ больше не требует указывать размер при его удалении.
При выполнении следующей программы выделяется память для 10-элементного массива типа double, который затем заполняется значениями от 100 до 109, после чего содержимое этого массива отображается на экране.
using namespace std;
p = new double [10]; // Выделяем память для 10-элементного массива.
//Заполняем массив значениями от 100 до 109.
for(i=0; i<10; i++) р[i] = 100.00 + i;
// Отображаем содержимое массива.
delete [] p; // Удаляем весь массив.
При динамическом выделении памяти для массива важно помнить, что его нельзя одновременно и инициализировать.
Динамическое выделение памяти в языке C++
Как вы уже знаете, при объявлении переменной необходимо указать тип данных, а для массива дополнительно задать точное количество элементов. На основе этой информации при запуске программы автоматически выделяется необходимый объем памяти. После завершения программы память автоматически освобождается. Иными словами, объем памяти необходимо знать до выполнения программы. Во время выполнения программы создать новую переменную или увеличить размер существующего массива нельзя.
Чтобы произвести увеличение массива во время выполнения программы необходимо выделить достаточный объем динамической памяти с помощью оператора new , перенести существующие элементы, а лишь затем добавить новые элементы. Управление динамической памятью полностью лежит на плечах программиста, поэтому после завершения работы с памятью необходимо самим возвратить память операционной системе с помощью оператора delete . Если память не возвратить операционной системе, то участок памяти станет недоступным для дальнейшего использования. Подобные ситуации приводят к утечке памяти.
Выделение памяти под один объект
Для выделения памяти под один объект предназначен следующий синтаксис:
Оператор new выделяет объем памяти, необходимый для хранения значения указанного типа, записывает в эту память начальное значение (если оно задано) и возвращает адрес. Работать в дальнейшем с этим участком памяти можно с помощью указателя. Пример выделения памяти:
При выделении памяти может возникнуть ситуация нехватки памяти. В случае ошибки оператор new возбуждает исключение bad_alloc (класс исключения объявлен в файле new ). Обработать это исключение можно с помощью конструкции try. catch . Пример выделения памяти с обработкой исключения:
Выделение памяти производится внутри блока try . Если при этом возникнет исключение bad_alloc , то управление будет передано в блок catch . После выполнения инструкций в блоке catch управление передается инструкции, расположенной сразу после блока. Иными словами, считается, что вы обработали исключение и можно продолжить выполнение программы. Следует учитывать, что пользоваться указателем после обработки нельзя, поэтому внутри блока catch обычно выводят сообщение об ошибке и завершают выполнение программы. Если исключение не обработать, то программа аварийно завершится. Если исключение не возникло, то инструкции внутри блока catch не выполняются.
Обратите внимание на то, что объявление указателя производится вне блока try . Если объявление разместить внутри блока, то область видимости переменной будет ограничена этим блоком. После выхода из блока переменная автоматически уничтожается, а выделенная память операционной системе не возвращается. Поэтому, объявление указателя должно находиться перед блоком, а не внутри него.
Возвратить ранее выделенную память операционной системе позволяет оператор delete . Оператор имеет следующий формат:
После использования оператора delete указатель по-прежнему будет содержать прежний адрес. Поэтому после использования оператора delete указатель принято обнулять. Пример выделения памяти под один объект приведен в листинге 3.14.
Листинг 3.14. Динамическое выделение памяти под один объект
Выделение памяти под массив
Выделение памяти под массив производится следующим образом:
Освободить выделенную память можно так:
Обратите внимание на то, что при освобождении памяти количество элементов не указывается. Пример выделения памяти под массив приведен в листинге 3.15.
Листинг 3.15. Динамическое выделение памяти под массив
Выделение памяти без возбуждения исключения
В ранних версиях C++ при нехватке памяти возвращался нулевой указатель. Такая же ситуация возникает в языке C при использовании функции malloc() . Чтобы оператор new , возвращал нулевой указатель, а не возбуждал исключение используется следующий синтаксис:
Для использования nothrow требуется подключить файл new . После выделения памяти следует проверить указатель на отсутствие нулевого значения. Пример выделения памяти без возбуждения исключения приведен в листинге 3.16.
Листинг 3.16. Динамическое выделение памяти без возбуждения исключения
Динамическое выделение памяти в языке C
Язык C++ поддерживает также функции malloc() , calloc() , realloc() и free() , позволяющие управлять динамической памятью в языке C. Хотя эти функции можно использовать и в языке C++, тем не менее стоит отдать предпочтение оператору new и явной обработке исключения. Описание этих функций приведено в книге лишь для того, чтобы вы могли разобраться в чужом коде.
Функции malloc() и free()
Для выделения динамической памяти в языке C предназначена функция malloc() . Прототип функции:
Функция malloc() принимает в качестве параметра размер памяти в байтах и возвращает указатель, имеющий тип void * . Если память выделить не удалось, то функция возвращает нулевой указатель. Все элементы будут иметь произвольное значение, так называемый «мусор». В языке C указатель типа void * неявно приводится к другому типу, поэтому использовать явное приведение не нужно. В языке C++ перед присвоением значения указателю необходимо выполнить явное приведение к используемому типу. Кроме того, чтобы программа была машинонезависимой следует применять оператор sizeof для вычисления размера памяти, требуемого для определенного типа.
Освободить ранее выделенную динамическую память позволяет функция free() . Функция принимает в качестве параметра указатель на ранее выделенную память и освобождает ее. Прототип функции:
Пример выделения памяти для одного объекта приведен в листинге 3.17.
Листинг 3.17. Динамическое выделение памяти для одного объекта
Пример выделения памяти под массив приведен в листинге 3.18.
Листинг 3.18. Динамическое выделение памяти под массив
Функция calloc()
Вместо функции malloc() можно воспользоваться функцией calloc() . Прототип функции:
В первом параметре функция calloc() принимает количество элементов, а во втором — размер одного элемента. Если память выделить не удалось, то функция возвращает нулевой указатель. Все элементы будут иметь значение 0 .
Используя функцию calloc() , следующую инструкцию из листинга 3.18:
мы можем записать так:
В качестве примера использования функции calloc() создадим двумерный массив (листинг 3.19). Для этого нам нужно создать массив указателей и в каждом элементе массива сохранить адрес строки. Память для каждой строки нужно выделить дополнительно.
Листинг 3.19. Динамическое выделение памяти под двумерный массив
Обратите внимание: при возвращении памяти вначале освобождается память, выделенная ранее под строки, а лишь затем освобождается память, выделенная ранее под массив указателей.
Так как мы сохраняем в массиве указателей лишь адрес строки, а не саму строку, количество элементов в строке может быть произвольным. Это обстоятельство позволяет создавать так называемые «зубчатые» двумерные массивы.
Строки в памяти могут быть расположены в разных местах, что не позволяет эффективно получать доступ к элементам двумерного массива. Чтобы доступ к элементам сделать максимально быстрым, можно представить двумерный массив в виде одномерного массива (листинг 3.20).
Листинг 3.20. Представление двумерного массива в виде одномерного
Так как в этом случае все элементы двумерного массива расположены в смежных ячейках, мы можем получить доступ к элементам с помощью указателя и адресной арифметики. Например, пронумеруем все элементы:
Функция realloc()
Функция realloc() выполняет перераспределение памяти. Прототип функции:
В первом параметре функция realloc() принимает указатель на ранее выделенную динамическую память, а во втором — новый требуемый размер в байтах. Функция выделит динамическую память длиной newSize , скопирует в нее элементы из старой области памяти, освободит старую память и вернет указатель на новую область памяти. Новые элементы будут иметь произвольные значения, так называемый «мусор». Если новая длина меньше старой длины, то лишние элементы будут удалены. Если память не может быть выделена, то функция вернет нулевой указатель, при этом старая область памяти не изменяется (в этом случае возможны утечки памяти, если значение присваивается прежнему указателю).
Если в первом параметре указать значение NULL , то будет выделена динамическая память и функция вернет указатель на нее. Если во втором параметре указано значение 0 , то ранее выделенная динамическая память освобождается и функция вернет нулевой указатель.
Пример использования функции realloc() приведен в листинге 3.21.
Листинг 3.21. Функция realloc()
В языке C++ вместо функций malloc() , calloc() и realloc() лучше использовать класс vector , который реализует динамический массив. Следить за размерами динамического массива нет необходимости, т. к. управление динамической памятью осуществляется автоматически:

Учебник C++ (Qt Creator и MinGW) в формате PDF
Помощь сайту
ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов
C++. Операторы new и delete[]. Выделение памяти для структурных переменных, объектов классов, массивов. Инициализация выделенной памяти. Пример перераспределения ранее выделенной памяти
Операторы new и delete[] . Выделение памяти для структурных переменных, объектов классов, массивов. Инициализация выделенной памяти. Пример перераспределения ранее выделенной памяти
Содержание
- 1. Пример динамического выделения памяти для структурной переменной
- 2. Пример динамического выделения памяти для объекта класса
- 3. Как выделить память для массива оператором new ? Общая форма
- 4. Как освободить память выделенную для массива оператором delete[] ? Общая форма
- 5. Пример динамического выделения и освобождения памяти для массива указателей на базовый тип
- 6. Пример выделения памяти для массива структурных переменных и его использование
- 7. Пример выделения и освобождения памяти для массива объектов. Инициализация массива объектов
- 8. Как перераспределить память, если нужно динамически увеличить (уменьшить) размер массива? Перераспределение памяти для структур, инициализация структур. Пример
Поиск на других ресурсах:
1. Пример динамического выделения памяти для структурной переменной
Выделение и освобождение памяти для структурной переменной. Пусть дана структура Date , которая имеет следующее описание:
Тогда, чтобы выделить и использовать память для переменной типа struct Date нужно написать приблизительно следующий код:
2. Пример динамического выделения памяти для объекта класса
В примере динамично выделяется память для указателя на объект класса CDayWeek . Пример реализован для приложения типа Console Application .
3. Как выделить память для массива оператором new ? Общая форма
Оператор new может быть использован для выделения памяти для массива. Общая форма оператора new в случае выделения памяти для массива:
- ptrArray – имя массива, для которого выделяется память;
- type – тип элементов массива. Тип элементов может быть базовый ( int , float , …) или другой структура, класс и т.п.);
- size – размер массива (количество элементов).
4. Как освободить память выделенную для массива оператором delete[] ? Общая форма
Для освобождения памяти, выделенной под массив, оператор delete имеет следующую форму использования:
где ptrArray – имя массива, для которого выделяется память.
5. Пример динамического выделения и освобождения памяти для массива указателей на базовый тип
В примере выделяется память для массива указателей на тип float . Затем элементы массива заполняются произвольными значениями. После этого, выделенная память освобождается оператором delete[] .
6. Пример выделения памяти для массива структурных переменных и его использование
В примере демонстрируется выделение и освобождение памяти для массива из 3-х структур типа TStudent . Также продемонстрированы способы доступа к полям заданного элемента в массиве структур.
7. Пример выделения и освобождения памяти для массива объектов. Инициализация массива объектов
В примере демонстрируется выделение памяти для массива объектов оператором new . После использования массива, происходит уничтожение выделенной памяти оператором delete .
В вышеприведенном коде, внутренняя переменная в массиве объектов инициализируется значением 1, так как такое значение задано в конструкторе без параметров CMonth()
Этот конструктор выступает инициализатором массива. Однако, в классе реализован еще один конструктор – конструктор с 1 параметром или параметризованный конструктор. Согласно синтаксису C++, массив объектов не может быть инициализирован параметризованным конструктором. Поэтому, в классе CMonth обязательно должен быть реализован конструктор без параметров.
Если конструктор без параметров CMonth() убрать из кода класса, то невозможно будет выделить память для массива объектов. Можно будет выделять память для одиночных объектов, но не для массива.
Вывод: если нужно выделить память для массива объектов некоторого класса, то этот класс обязательно должен иметь реализацию конструктора без параметров.
8. Как перераспределить память, если нужно динамически увеличить (уменьшить) размер массива? Перераспределение памяти для структур, инициализация структур. Пример
В примере демонстрируется процесс перераспределения памяти для типа структуры DayWeek . Выделение и перераспределение памяти динамически есть основным преимуществом этого способа по сравнению со статическим выделением памяти. Память в программе можно выделять когда нужно и сколько нужно.
В структуре DayWeek реализован конструктор без параметров (по умолчанию), который инициализирует массив структур значением по умолчанию ( d =1).
В функции main() сначала выделяется память для массива из 5 структур. Затем эта память перераспределяется для массива из 7 структур. Для этого используется дополнительный указатель p2 .
При перераспределении сначала память выделяется для p2 (7 элементов). Затем копируются данные из p в p2 . После этого освобождается память, которая была выделена для указателя p (5 элементов).
На следующем шаге значение p устанавливается равным значению p2 . Таким образом, оба указателя указывают на одну и ту же область памяти.
Динамическая память
При создании массива с фиксированными размерами под него выделяется определенная память. Например, пусть у нас будет массив с пятью элементами:
Для такого массива выделяется память 5 * 8 (размер типа double) = 40 байт. Таким образом, мы точно знаем, сколько в массиве элементов и сколько он занимает памяти. Однако это не всегда удобно. Иногда бывает необходимо, чтобы количество элементов и соответственно размер выделяемой памяти для массива определялись динамически в зависимости от некоторых условий. Например, пользователь сам может вводить размер массива. И в этом случае для создания массива мы можем использовать динамическое выделение памяти.
Для управления динамическим выделением памяти используется ряд функций, которые определены в заголовочном файле stdlib.h :
malloc() . Имеет прототип
Выделяет память длиной в s байт и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL
calloc() . Имеет прототип
Выделяет память для n элементов по m байт каждый и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL
realloc() . Имеет прототип
Изменяет размер ранее выделенного блока памяти, на начало которого указывает указатель bl, до размера в ns байт. Если указатель bl имеет значение NULL , то есть память не выделялась, то действие функции аналогично действию malloc
free() . Имеет прототип
Освобождает ранее выделенный блок памяти, на начало которого указывает указатель bl.
malloc
Функция malloc() выделяет память длиной для определенного количества байт и возвращает указатель на начало выделенной памяти. Через полученный указатель мы можем помещать данные в выделенную память. Рассмотрим простой пример:
Здесь с помощью функции malloc выделяется память для одного объекта int . Чтобы узнать, сколько байтов надо выделить, передаем в функцию malloc размер типа int на текущей и в результате получаем указатель ptr , который указывает на выделенную память
То есть поскольку int на большинстве архитектур занимает 4 байта, то в большинстве случаев будет выделяться память объемом в 4 байта. Стоит отметить, что мы также могли бы получить размер через разыменование указателя:
Для универсальности возвращаемого значения в качестве результата функция malloc() (как и calloc() и realloc() ) возвращает указатель типа void * . Но в нашем случае создается массив типа int, для управления которым используется указатель типа int * , поэтому выполняется неявное приведение результата функции malloc к типу int * .
Далее через этот указатель с помощью операции разыменования помещаем в выделенный участок памяти число 24:
В дальнейшем, используя указатель, можно получить значение из выделенного участка памяти:
После завершения работы освобождаем память, передавая указатель в функцию free() :
Стоит отметить, что теоретически мы можем столкнуться с тем, что функции malloc() не удастся выделить требуемую память, и тогда она возвратит NULL. Чтобы избежать подобной ситуации перед использованием указателя мы можем проверять его на значение NULL:
Немотря на освобождение памяти с помощью функции free() указатель сохраняет свой адрес, и теоретически мы можем обращаться к памяти по данному указателю. Однако полученные значения уже будут неопределенными и недетеминированными. Поэтому некоторые советуют после освобождения памяти также устанавливать для указателя значение NULL :
Выделение памяти для массива
Подобным образом можно выделять память и под набор объектов. Например, выделим память для массива из 4-х чисел int:
Выделение памяти для структуры
Аналогичным образом можно выделять память для одной или набора структур:
calloc
Функция calloc() имеет прототип
Она выделяет память для n элементов по m байт каждый и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL
В отличие от функции malloc() она инициализирует все выделенные байты памяти нулями. Например, выделим память для одного объекта int :
Подобным образом можно выделить память и для других объектов. Например, выделим память для массива из 4-х объектов int :
realloc
Функция realloc() позволяет изменить размер памяти, ранее выделенной с помощью функций malloc() b calloc() . Имеет прототип
Первый параметр представляет указатель на ранее выделенный блок памяти. А второй параметр представляет новый размер блока памяти в байтах.
Если указатель bl имеет значение NULL , то есть память не выделялась, то действие функции аналогично действию malloc
Рассмотрим небольшой пример:
Здесь сначала выделяем память для одного объекта int с помощью функции malloc.
Если память успешно выделена, то выводим на консоль адрес и размер выделенного блока памяти. Затем с помощью функции realloc расширяем память до 4 объектов int
Если увеличение памяти прошло успешно, то заново выводим данные на консоль и освобождаем память по новому указателю. Если увеличение памяти прошло не удачно, то освобождаем память но старому указателю.
Консольный вывод в моем случае
Стоит отметить, что нам необязательно создавать новый указатель, мы можем присвоить значение старому указателю: