Тут я как всегда не вовремя задумался вот над каким вопросом. При выделении памяти из кучи очевидно, что в куче должна сохраняться информация о размере запрошенной области памяти и о количестве запрошенных элементов. Зачем же тогда существуют отдельно операторы delete
и delete[]
? Ведь независимо от того, запросили мы вектор или один элемент, в куче есть информация и о размере запрошенной области, и о количестве запрошенных элементов. Не может быть, чтобы при запросе одного элемента в куче не сохранялась информация о том, что запрошен один элемент. А если это так, то оператор delete
вполне может разобраться (по служебной информации, содержащейся в куче) был ли запрошен массив или был запрошен один элемент. И, соответственно, вернуть в кучу память или одного элемента, или вектора. Получается, что оператор delete[]
избыточен.
UPD1:
В нынешнем подходе С++ поступает более экономично: хранит свою дополнительную информацию только в массивах объектов с нетривиальным деструктором.
То есть Вы хотите сказать, что у кучи есть несколько форматов? Один формат для запрошенного одного элемента, другой формат для запрошенного вектора, третий формат для запрошенного одного элемента с нетривиальным аллокатором/деструктором, четвертый формат для запрошенного вектора с нетривиальным аллокатором/деструктором? Ну, это дело конечно разработчиков компилятора и кучи, как там они видят свою задачу чтобы сделать ее максимально эффективной. Но на первый взгляд иметь много форматов кучи это не так чтобы однозначно было эффективнее, чем хранить всю информацию и в рантайме разбираться, что же именно там сейчас лежит. Тем более, что разбираться в рантайме и при подходе со многими форматами кучи все равно придется.
UPD2:
И да, нетривиальный аллокатор тоже хранит в куче информацию о количестве запрошенных элементов и о размере одного элемента. Имея эту информацию нетривиальный деструктор может разобраться, что именно ему надо удалять. И опять же в этом случае не нужен оператор delete[], достаточно оператора delete.
UPD3:
Оставим пока в покое нетривиальные аллокаторы. Рассмотрим пока что стандартные аллокаторы, тем более что проблемы в обоих случаях одинаковы.
Итак, есть куча и есть операторы new и new[]. Оба оператора обязаны занести в служебную информацию кучи данные о размере одного объекта и о КОЛИЧЕСТВЕ ОБЪЕКТОВ в запросе. Соответственно, оператор возврата памяти delete нужен только один, так как по служебной информации рантайм может и должен разобраться сколько именно объектов было запрошено. Соответственно, оператор delete[] избыточен.
Теперь рассмотрим нестандартные (пользовательские) аллокаторы. Совершенно так же пользовательские new и new[] обязаны занести в служебную информацию кучи данные о размере одного объекта и о КОЛИЧЕСТВЕ ОБЪЕКТОВ в запросе. Дополнительно пользовательские new и new[] обязаны занести в служебную информацию кучи указатель на пользовательский деструктор. Опять же в этом случае оператор возврата памяти delete нужен только один, так как по служебной информации рантайм может и должен разобраться сколько именно объектов было запрошено. Соответственно, оператор delete[] избыточен.
Во-первых, даже если в куче и сохраняется информация о размере запрошенного блока в байтах, способ хранения этой информации может быть известен delete
только в том случае, если используется "штатный" аллокатор. Но процесс выделения "сырой" памяти в С++ является перегружаемым пользователем. Как только выделение памяти перешло на пользовательский аллокатор, delete
уже не может определить размер блока.
Во-вторых, даже если размер блока в байтах известен, по этому размеру все равно нельзя однозначно восстановить точное количество элементов в массиве, чтобы вызвать точное количество деструкторов. Размер блока может превышать точное значение, требуемое для хранения элементов.
В-третьих, не ясно, о каком "количестве запрошенных элементов" вы говорите. Количество запрошенных элементов сохраняет именно new []
и вычитывает именно delete []
. Для того, собственно, delete []
и сделан отдельным от delete
. Смотрите детали здесь: Откуда C/C++ знает сколько надо освободить памяти, если не знает размер массива?
Теоретически можно сделать "умный delete
", который сам всегда во всем разбирается. Но это приведет к безусловной необходимости хранить дополнительную информацию во всех блоках памяти. В нынешнем подходе С++ поступает более экономично: хранит свою дополнительную информацию только в массивах объектов с нетривиальным деструктором.
Фактически почти такой "умный delete
" у вас уже есть. Никто вам не запрещает везде просто безусловно пользоваться new[]/delete[]
и забыть про существование new/delete
. То есть одиночные объекты просто выделять как массивы размера 1. Но это будет несколько более расточительно (и не поддерживает полиморфного удаления).
Отвечая на ваш UPD1:
В типичной реализации у блока в С++ куче фактически три формата: для одиночного объекта (new
), для массива с тривиальными деструкторами (new[]
) и для массива с нетривиальными деструкторами (new[]
).
При этом первые два формата можно было бы считать совпадающими с точки зрения внутренней структуры, т.к. это просто "блоки памяти". Но тут вмешивается тот факт, что механизмы выделения/освобождения "сырой" памяти в С++ являются перегружаемыми пользователем: независимо для new/delete
и для new[]/delete[]
. Поэтому это - отдельные форматы.
Ответ на ваш UPD3:
Я не знаю, с чего вы взяли, что "Оба оператора обязаны занести в служебную информацию кучи данные о размере одного объекта и о КОЛИЧЕСТВЕ ОБЪЕКТОВ в запросе". Это совершенно не так.
Еще раз: просто new
такой информации НЕ сохраняет. И new[]
для типов с тривиальным деструктором никакой информации о количестве или размере объектов НЕ хранит тоже. Эти форматы просто выделяют память через обычный malloc
, освобождают через обычный free
и никакой дополнительной внутренней информации в этом блоке памяти не сохраняют. С точки зрения С++ памяти требуется ровно столько, сколько нужно для хранения пользовательских данных.
Особняком стоит только new[]
для массива с нетривиальными деструкторами. Только он сохраняет в блоке служебную информацию о точном количестве элементов в массиве (и поэтому выделяет несколько больше памяти, чем требуется для пользовательских данных).
Информация о размере одного элемента в таком блоке не хранится вообще никогда - это низачем не нужно.
Я при этом говорю только о стандартных аллокаторах. Пользовательские аллокаторы тут ни при чем.
Вы смешиваете в одну кучу интерфейс и детали реализации. И, вероятнее всего, забываете, что С++ используется не только на x86-совмесимых системах.
Язык (интерфейс) ничего не знает о куче. Это реализация. New вполне может выделять память, например, из slab-аллокатора, в то время как New[] будет использовать кучу.
К тому же на микроконтроллерах есть серьезное ограничение по памяти, и делать одинаковую реализацию New/Delete и New[]/Delete[] просто расточительно, т.к. вторая пара должна знать не только размер участка памяти, но и количество реальных элементов в нем.
Дело в том что хотя рантайм и знает размер(что в общем случае не верно, он может размер и не хранить), он не знает что там содержится. Теоретически можно попытаться угадать, но тогда алгоритм угадывания придется описать в спецификации. Это ненужное усложнение.
Краткий ответ: чтобы не усложнять спецификацию и оставить ее более гибкой.
Насколько мне известно, существование new / delete
и new[] / delete[]
обосновано девизом: не платим за то, что не используем
.
Такой подход позволяет экономить память и такты на лишних проверках.
Безусловно, менеджер памяти хранит информацию о размере каждого блока, однако одного лишь размера блока памяти недостаточно, чтобы корректно обработать удаление массива объектов, вызвав для каждого объекта его деструктор.
В C
такой проблемы разделения нет, потому что free()
просто освобождает память, не вызывая деструкторы.
Если же мы попытаемся удалить массив объектов C++
, используя только информацию о размере блока памяти, то мы не сможем этого сделать. Нам не хватает информации.
Поэтому, new[]
заводит для каждого массива объектов минимум два счетчика. Содержимое этих счетчиков зависит от реализации компилятора, но обычно в счетчиках хранятся размер блока памяти
и тип объекта
. Зная тип объекта - мы имеем информацию о размере объекта и его деструкторе.
Только в этом случае информации становится достаточно, чтобы корректно вызвать деструктор для каждого объекта массива, а затем освободить блок памяти.
Если бы существовал только delete
, который умел бы работать и с одиночными объектами, и с массивами объектов, тогда при работе с одиночными объектами происходил бы перерасход памяти и тактов процессора на лишние проверки.
Подтверждением этой теории служит тот факт, что следующий код:
while (1)
{
float *arr = new float[1000];
delete arr;
arr = new float;
delete[] arr;
}
В большинстве ситуацией не приводит к падениям или утечкам. Да, это UB
, но я видел множество случаев такого кода, который был написан умышленно и работал годами.
Поскольку float
не имеет деструктора, то и проблем при перепутывании delete
и delete[]
чаще всего не возникает.
Конечно, внутри все это устроено намного сложнее, но общая суть происходящего примерно такая.
Фрилансер или Digital-агентство - Как сделать правильный выбор?
Нужна помощь в определении необходимых библиотек, чтобы реализовать приложение в котором будут воспроизводиться музыкальные файлы и различные...
Цель: в edittext убрать пробелы в начале и конце строки
У меня модель пользователя, в которую следует добавить поле json, значения которого будут разными, то есть не знаю как создать его