Почему [] используется в delete (delete []) для освобождения динамически выделенного массива?

Я знаю, что когда delete [] приведет к разрушению всех элементов массива, а затем освободит память.

Я изначально думал, что компилятор хочет, чтобы он просто вызывал деструктор для всех элементов массива, но у меня также есть контраргумент для того, что:

распределитель кучи памяти должен знать размер выделенных байтов и использовать sizeof(Type) его можно найти нет элементов и вызвать соответствующий нет деструкторов для массива, чтобы предотвратить ресурс подтеки.

Итак, мое предположение верно или нет, и, пожалуйста, очистите мои сомнения.

поэтому я не получаю использование [] на delete [] ?

5 ответов


Скотт Мейерс говорит в своей эффективной книге на C++: пункт 5: Используйте ту же форму в соответствующих случаях new и delete.

большой вопрос для удаления заключается в следующем: сколько объектов находится в удаляемой памяти? Ответ на этот вопрос определяет, сколько деструкторов необходимо вызвать.

указывает ли удаляемый указатель на один объект или на массив объектов? Единственный способ для delete узнать - это рассказать об этом. Если вы не используете скобки при использовании delete предполагается, что на один объект указывает delete.

кроме того, распределитель памяти может выделить больше места, необходимого для хранения ваших объектов, и в этом случае разделение размера блока памяти, возвращаемого размером каждого объекта, не будет работать.

в зависимости от платформы _msize (windows),malloc_usable_size (linux) или malloc_size (osx) функции расскажут вам реальную длину блока, который был выделен. Этот информацию можно использовать при проектировании растущих контейнеров.

еще одна причина, почему это не сработает, это Foo* foo = new Foo[10] звонки operator new[] для выделения памяти. Тогда delete [] foo; звонки operator delete[] чтобы освободить память. Поскольку эти операторы могут быть перегружены,вы должны придерживаться Конвенции в противном случае delete foo; звонки operator delete который может иметь несовместимую реализацию с operator delete []. это вопрос семантики, а не просто отслеживания количества выделенных объект, чтобы позже выдать правильное количество вызовов деструктора.

Читайте также:

[16.14] после p = new Fred[n], как компилятор знает, что есть n объектов, которые будут уничтожены во время delete [] p?

короткий ответ: магия.

длинный ответ: система времени выполнения хранит количество объектов, n, где-то, где его можно получить, если вы знаете только указатель, p. Есть два популярных метода, которые делают этот. Оба эти метода используются коммерческими компиляторами, оба имеют компромиссы, и ни один из них не является идеальным. Эти методы:


EDIT: прочитав комментарии @AndreyT, я зарылся в свою копию Stroustrup's "Дизайн и эволюция C++" и выдержка из следующего:

как мы гарантируем, что массив правильно удален? В частности, как мы можем гарантировать, что деструктор вызывается для всех элементов массива?

...

простое удаление не требуется для обработки как отдельных объектов, так и массивов. Это позволяет избежать усложнения общего случая выделения и освобождения отдельных объектов. Он также избегает обременять индивидуала объекты с информацией, необходимой для освобождения массива.

промежуточная версия delete[] требовала от программиста указания количества элементов массива.

...

это оказалось слишком подверженным ошибкам,поэтому бремя отслеживания количества элементов было возложено на реализацию.

как упоминал @Marcus, рациональным может быть "вы не платите за то, чего не делаете использовать."


EDIT2:

в" языке программирования C++, 3-е издание", §10.4.7, Бьярне Страуструп пишет:

точно, как выделяются массивы и отдельные объекты, зависит от реализации. Поэтому различные реализации будут по-разному реагировать на неправильное использование операторов delete и delete []. В простых и неинтересных случаях, таких как предыдущий, компилятор может обнаружить проблему, но обычно что-то неприятное произойдет во время выполнения.

специальный оператор уничтожения для массивов, delete [], логически не требуется. Однако предположим, что реализация свободного хранилища требовалась для хранения достаточной информации для каждого объекта, чтобы определить, является ли он отдельным или массивом. Пользователь мог бы быть освобожден от бремени, но это обязательство наложило бы значительные временные и пространственные накладные расходы на некоторые реализации c++.


основная причина, почему было решено держать отдельно delete и delete[] заключается в том, что эти две сущности не так похожи, как может показаться на первый взгляд. Для наивного наблюдателя они могут показаться почти одинаковыми: просто разрушаются и освобождаются, с единственной разницей в потенциальном количестве обрабатываемых объектов. На самом деле разница гораздо более существенна.

самое важное различие между этими двумя является то, что delete может выполнять полиморфное удаление объектов, т. е. статический тип объекта может отличаться от его динамического типа. delete[] С другой стороны должны заниматься строго запрещено-полиморфное удаление массивов. Таким образом, внутренне эти две сущности реализуют логику, которая существенно отличается и не пересекается между ними. Из-за возможности полиморфного удаления, функциональность delete даже отдаленно не совпадает с функциональностью delete[] на массив из 1 элемента, как наивный наблюдатель мог бы ошибочно предположить.

вопреки странным утверждениям, сделанным в некоторых других ответах, это, конечно, вполне возможно заменить delete и delete[] только с одной конструкцией, которая будет ветвиться на самой ранней стадии, т. е. она определит тип блока памяти (массив или нет), используя информацию о домохозяйстве, которая будет храниться new/new[], а затем перейти к соответствующей функциональностью, эквивалентной либо delete или delete[]. Однако это было бы довольно плохое дизайнерское решение, так как, опять же, функциональность двух слишком отличается. Принудить обоих к одной конструкции было бы сродни созданию швейцарского армейского ножа с функцией освобождения. Кроме того, чтобы иметь возможность отличить массив от не-массива, нам пришлось бы ввести дополнительную часть бытовой информации даже в однообъектные выделения памяти, выполненные с помощью plain new. Это может легко привести к заметным память в один распределении объектов.

но, еще раз, основная причина здесь-функциональная разница между delete и delete[]. Эти языковые сущности обладают лишь кажущимся поверхностным сходством, которое существует только на уровне наивной спецификации ("деструкт и свободная память"), но как только человек понимает в деталях, что эти сущности действительно должны делать, он понимает, что они слишком разные, чтобы быть объединены в одно.

P. S. Это кстати одна проблем с предложением о sizeof(type) вы сделали в вопрос. Из-за потенциально полиморфной природы delete, вы не знаете тип в delete, поэтому вы не можете получить любой sizeof(type). С этой идеей больше проблем, но этого уже достаточно, чтобы объяснить, почему она не будет летать.


куча сама знает размер выделенного блока - вам нужен только адрес. Похоже, free () работает - вы только передаете адрес, и он освобождает память.

разницу между delete (delete[]) и free() это то, что первые два сначала вызывают деструкторы, а затем свободную память (возможно, используя free()). Проблема в том, что delete[] также имеет только один аргумент-адрес и, имея только этот адрес, он должен знать количество объектов для запуска деструкторов. Так new[] использует реализацию som-определенный способ записи где - то количества элементов-обычно он добавляет массив с количеством элементов. Теперь delete[] будет полагаться на эти данные реализации для запуска деструкторов, а затем свободной памяти (опять же, только с использованием адреса блока).


delete[] просто вызывает другую реализацию (функцию);

нет причин для распределителя не мог отслеживать его (в самом деле, было бы достаточно легко написать свой собственный).

Я не знаю, почему им это не удалось, или историю реализации, если бы я догадался: многие из них " ну, почему это не было немного проще?"вопросы (на C++) сводились к одному или нескольким из:

  1. совместимость с C
  2. производительность

в этом случае, производительность. Используя delete vs delete[] достаточно просто, я считаю, что все это может быть абстрагировано от программиста и быть достаточно быстрым (для общего использования). delete[] требуется только несколько дополнительных вызовов функций и операций (исключая вызовы деструктора), но это за вызов delete, и ненужный, потому что программист обычно знает тип, с которым он / она имеет дело (если нет, скорее всего, большая проблема под рукой). Поэтому он просто избегает вызова через распределитель. Кроме того, эти одиночные распределения, возможно, не должны отслеживаться распределителем так подробно; обработка каждого распределения как массива потребует дополнительных записей для подсчета для тривиальных распределений, поэтому это несколько уровней простых упрощений реализации распределителя, которые на самом деле важны для многих людей, учитывая, что это очень низкоуровневый домен.


Это сложнее.

ключевое слово и соглашение об использовании его для удаления массива были изобретены для удобства реализаций, и некоторые реализации используют его (я не знаю, какой. MS VC++ не делает).

удобство заключается в следующем:

во всех остальных случаях, вы знаете точный размер, чтобы быть освобождены другими средствами. Когда вы удаляете один объект, вы можете иметь размер из sizeof () времени компиляции. При удалении полиморфного объект по базовому указателю и у вас есть виртуальный деструктор, вы можете иметь размер как отдельную запись в vtbl. Если вы удалите массив, как вы узнаете размер освобождаемой памяти, если вы не отслеживаете ее отдельно?

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