Что такое динамическое распределение памяти на С++?
Я изучаю динамическое распределение памяти на С++ и упоминаются ключевые слова new
и new[]
.
Говорят, что он позволяет пользователям указывать размер выделения памяти во время выполнения, в отличие от простого объявления переменной или массива с фиксированным размером в исходном коде.
Я не понимаю эту концепцию. Как это работает? Мне просто нужно разъяснить идею, и пример будет полезен!
Ответы
Ответ 1
Итак, если вам нужен массив из 10 целых чисел, вы должны написать:
int arr[10];
Но что, если вы хотите сделать что-то подобное:
cout << "How many?";
cin >> num;
int arr[num];
Ну, язык С++ этого не позволяет. Вместо этого вам нужно сделать:
int *arr = new int[num];
чтобы создать массив. И позже вы ДОЛЖНЫ [1] использовать:
delete [] arr;
чтобы освободить память.
Итак, как это работает? Когда вы вызываете new, библиотека времени выполнения С++ [код, который вам не нужно писать, составляющий основу С++], будет определять, сколько пробелов в пространстве num
заняло бы, и найти для этого некоторое пространство в памяти. Я не буду вдаваться в подробности о том, "как вы находите какую-то память". На данный момент, просто доверяйте мне, есть некоторая память, доступная где-то, которая может быть использована для хранения некоторых целых чисел.
Когда вы позже звоните в delete
, эта же память возвращается обратно в "пул" или "кучу" памяти, из которой она появилась.
Конечно, если у вас есть машина с, скажем, 256 МБ памяти, и вы пытаетесь попросить пространство для хранения 250 миллионов целых чисел, имея в виду, что целое число занимает более одного байта, оно не собирается работайте - здесь нет "волшебства" - память по-прежнему ограничена тем, насколько доступно в машине... Вы просто имеете право определять в программе, когда она работает, сколько памяти вам нужно, а чем решать, когда ПИСЬЕТ программу.
Изменить: обычно лучше всего "скрыть" распределение памяти, используя уже существующие "контейнеры" и "оберточные классы", которые полезны для этой цели. Например:
std::vector<int> arr;
будет работать как хранилище переменных для целых чисел, и вам никогда не придется беспокоиться о том, чтобы освободить память или даже узнать, сколько вам нужно, прежде чем вы их сохранили.
std::shared_ptr<int> arr = new int[num];
- это еще один случай, когда "shared_ptr" больше не используется [он отслеживает, что внутри класса общего указателя, поэтому вам не нужно заботиться о освобождении памяти].
[1] Если вы не хотите утечки памяти, и это "плохой стиль" для утечки памяти. Не делайте никого счастливым, если вы это сделаете.
Ответ 2
Я видел много сообщений о распределении памяти на С++, вопросы о "новом операторе" и "операторе new", вопросы о new int(100)
vs new int[100]
, вопросы об инициализации памяти... Я думаю, что должно быть ответ, который суммирует все ясно раз и навсегда, и я выбираю этот вопрос, чтобы написать это резюме. Речь идет о динамическом распределении памяти, т.е. Распределении по куче во время выполнения. Я также предоставляю итоговую реализацию (public domain).
C vs С++
Основные функции для распределения динамической памяти:
- В C (заголовок
<cstdlib>
) мы имеем главным образом malloc
и calloc
и free
. Я не буду говорить о realloc
.
- в С++ (заголовок
<new>
), мы имеем:
- Шаблон размещения одного объекта с аргументами инициализации:
-
new T( args )
-
new (std::nothrow) T( args )
-
delete ( T* )
- Распределение шаблонов нескольких объектов с инициализацией по умолчанию:
-
new T[ size_t ]
-
new (std::nothrow) T[ size_t ]
-
delete[] ( T* )
- Инициализация памяти шаблона без выделения для одного или нескольких объектов:
-
new (void*) T( args )
-
new (void*) T[ size_t ]
- Внутренние новые выражения для:
- Распределение исходной памяти
::operator new( size_t )
;
- Исходное распределение памяти без исключения
::operator new( size_t, std::nothrow )
;
- Инициализация исходной памяти без выделения
::operator new( size_t, ptr )
.
Пожалуйста, посмотрите этот пост для краткого сравнения.
Динамические распределения Legacy C
Основные моменты: полное стирание типа (void*
указатели), и поэтому нет конструкции/уничтожения, размер указан в байтах (обычно с использованием sizeof
).
malloc( size_t )
не инициализирует память вообще (необработанная память содержит мусор, всегда инициализируется вручную перед использованием). calloc( size_t, size_t )
инициализирует все биты до 0 (небольшие служебные данные, но полезны для числовых типов POD). Любая выделенная память должна быть освобождена с помощью free
ТОЛЬКО.
Построение/уничтожение экземпляров класса должно выполняться вручную перед использованием/перед выпуском памяти.
Динамические распределения С++
Основные моменты: запутанный из-за похожих синтаксисов, выполняющих разные вещи, все delete
-статы называют деструктор, все delete
-statements принимают полностью типизированные указатели, некоторые new
-statements возвращают полностью типизированные указатели, some new
-statements вызывают некоторый конструктор.
Предупреждение: как вы увидите ниже, new
может быть либо функцией ключевого слова ИЛИ. Лучше не говорить о "новом операторе" и/или "операторе new", чтобы избегать путаницы. Я называю "new
-statements" любыми действительными операторами, которые содержат new
как функцию или ключевое слово. Люди также говорят о "new
-выражениях", где new
- это ключевое слово, а не функция.
Распределение сырой памяти (без инициализации)
Не используйте это самостоятельно. Это внутреннее использование новых выражений (см. ниже).
Эти распределения не не инициализируют память, и, в частности, они не вызывают конструктор по умолчанию для выделенных объектов. Поэтому вы ДОЛЖНЫ инициализировать ВСЕ элементы вручную, прежде чем освободить выделение, используя delete
или delete[]
.
Примечание. Я не мог подчеркнуть, что вы НЕ должны использовать это самостоятельно. Однако, если вы должны использовать его, убедитесь, что вы указали указатель на void
вместо введенного указателя при вызове delete
или delete[]
на таких распределениях (всегда после инициализации вручную). Я лично испытал ошибки времени выполнения с не-POD-типами с некоторыми компиляторами (возможно, моя ошибка).
Инициализация исходной памяти (без распределения)
Не используйте это самостоятельно. Это используется внутренне с помощью новых выражений (см. ниже).
В следующем случае я предполагаю void *ptr = ::operator new( n*sizeof(T) )
для некоторого типа T
и размера n
.
Затем ::operator new( n*sizeof(T), (T*) ptr )
инициализирует элементы n
типа T
, начиная с ptr
, используя конструктор по умолчанию T::T()
. Здесь нет распределения, только инициализация с использованием конструктора по умолчанию.
Выделение и инициализация одного объекта
-
new T( args )
выделяет и инициализирует память для одного объекта типа T
с помощью конструктора T::T( args )
. Конструктор по умолчанию не будет вызываться, если аргументы не будут опущены (т.е. new T()
или даже new T
). Выдает исключение std::bad_alloc
при сбое.
- То же самое для
new (std::nothrow) T( args )
, за исключением того, что он возвращает NULL
в случае сбоя.
- Используйте
delete
для вызова деструктора T::~T()
и отпустите соответствующую память.
Распределение и инициализация нескольких объектов
-
new T[n]
выделяет и инициализирует память для объектов n
типа T
с использованием конструктора по умолчанию. Выдает исключение std::bad_alloc
при сбое.
- Идем для
new (std::nothrow) T[n]
, за исключением того, что он возвращает NULL
в случае сбоя.
- Используйте
delete[]
для вызова деструктора T::~T()
для каждого элемента и отпустите соответствующую память.
Инициализация памяти (также называемая "размещение новой" )
Здесь нет выделения. Независимо от того, как было выполнено выделение:
-
new (ptr) T(args)
вызывает конструктор T::T(args)
в памяти, хранящейся в ptr
. Конструктор по умолчанию не вызывается, если аргументы не опущены.
-
new (ptr) T[n]
вызывает конструктор по умолчанию T::T()
на n
объекты типа T
, хранящиеся от ptr
до ptr+n
(т.е. n*sizeof(T)
байты).
Связанные записи