Почему синтаксис delete [] существует в C ++?

Каждый раз, когда кто-то задает здесь вопрос о delete[], всегда есть довольно общие сведения о том, как это делает C ++, используйте тип ответа delete[]. Исходя из ванильного фона C, я не понимаю, почему вообще нужен другой вызов.

С _3 _ / _ 4_ вы можете получить указатель на непрерывный блок памяти и освободить блок непрерывной памяти. Что-то в области реализации приходит и знает, какой размер выделенного вами блока был основан на базовом адресе, когда вам нужно его освободить.

Нет функции free_array(). Я видел несколько сумасшедших теорий по другим вопросам, косвенно связанным с этим, например, вызов delete ptr освободит только верхнюю часть массива, а не весь массив. Вернее, не определяется реализацией. И конечно ... если бы это была первая версия C ++, и вы сделали странный выбор дизайна, который имеет смысл. Но почему со стандартом C ++ $PRESENT_YEAR он не был перегружен ???

Кажется, что единственный дополнительный бит, который добавляет C ++, - это прохождение массива и вызов деструкторов, и я думаю, что, возможно, в этом его суть, и он буквально использует отдельную функцию, чтобы сэкономить нам один поиск длины времени выполнения, или nullptr в конце списка в обмен на мучения каждого нового программиста на C ++ или программиста, у которого был нечеткий день и который забыл, что есть другое зарезервированное слово.

Может ли кто-нибудь прояснить раз и навсегда, есть ли причина помимо того, что говорится в стандарте, и никто не ставит под сомнение?


person awiebe    schedule 17.05.2021    source источник
comment
Если вы хотите проверить распределение и освобождение памяти, чтобы увидеть, верны ли эти сумасшедшие теории или нет, вы можете использовать Valgrind, чтобы увидеть, что на самом деле происходит. Я подозреваю, что при удалении перегрузки возникает больше проблем, чем описано в ответах, но у меня нет опыта.   -  person ttbek    schedule 18.05.2021
comment
Связанный вопрос: Как delete [] узнает, что это массив?, и особенно примечание этот ответ.   -  person Fred Larson    schedule 18.05.2021


Ответы (7)


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

Чтобы иметь только одну версию, реализации потребуется механизм для отслеживания дополнительной информации о каждом указателе. Но один из основополагающих принципов C ++ заключается в том, что пользователя не следует заставлять оплачивать затраты, которые ему совершенно не нужны.

Всегда delete то, что вы new и всегда delete[] то, что new[]. Но в современном C ++ new и new[] обычно больше не используются. Используйте std::make_unique, _ 10_, _ 11_ или другие более выразительные и безопасные альтернативы.

person François Andrieux    schedule 17.05.2021
comment
Вау, это был быстрый ответ, спасибо за подсказки по функциям распределения. Удивительно, как часто ответ на C ++ - не использовать это ключевое слово, использовать std :: someWeirdFunctionIntroducedInC ++ ›= 11 () - person awiebe; 18.05.2021
comment
@awiebe C ++ дает вам инструменты для работы как можно ближе к оборудованию. Но эти инструменты обычно мощные и грубые, что делает их опасными и трудными для эффективного использования. Таким образом, он также предоставляет инструменты через стандартную библиотеку, которые немного дальше удалены от оборудования, но очень безопасны и просты. Вот почему вы изучаете так много функций, но вам говорят не использовать их. Потому что, если вы не делаете что-то очень уникальное или странное, эти низкоуровневые инструменты бесполезны. Более удобные функции, как правило, подходят. - person François Andrieux; 18.05.2021
comment
@awiebe Вы правы в том, что в большинстве случаев, если есть аккуратная стандартная библиотечная функция, заменяющая встроенный механизм, она исходит из C ++ 11 или новее. C ++ 11 в корне произвел революцию в языке, позволив стандартные функции библиотеки, которые ранее было невозможно реализовать. Разница между C ++ 11 и предыдущими версиями настолько значительна, что их можно рассматривать как два разных языка. При изучении C ++ следует различать учебные материалы, ориентированные на C ++ 03 и более ранние версии, от материалов, предназначенных для C ++ 11 и более поздних версий. - person François Andrieux; 18.05.2021
comment
@awiebe, также обратите внимание, что наличие механизмов более низкого уровня, таких как new, позволяет писать большую часть стандартной библиотеки (и других библиотек) на чистом C ++ (некоторые части могут нуждаться в поддержке компилятора). Таким образом, совет также может заключаться в использовании их только для построения абстракций более высокого уровня. - person Carsten S; 18.05.2021
comment
Этот ответ кажется неправильным. И delete, и delete[] вызывают деструкторы. - person Barmar; 18.05.2021
comment
@Barmar Я переформулировал его, чтобы было понятнее, что delete[] потенциально имеет дополнительные накладные расходы, связанные с уничтожением каждого элемента массива. - person François Andrieux; 18.05.2021
comment
Избегание тривиального цикла в случае с одним элементом кажется любопытной микрооптимизацией, но, возможно, это имело смысл в первые дни, когда компьютеры были медленнее, а C ++ по духу был ближе к C. - person Barmar; 18.05.2021
comment
@Barmar Я думаю, что проблема была больше в накладных расходах памяти, чем в скорости. Проблема становится более очевидной при размещении new, когда переносимое размещение массива new в настоящее время невозможно из-за этих потенциальных накладных расходов. См. Может ли размещение новых массивов использоваться в портативный способ?. Возможно, вам потребуется передать больше памяти, чем потребуется элементам, но невозможно спросить у реализации, сколько дополнительного хранилища ей потребуется. - person François Andrieux; 18.05.2021
comment
@ FrançoisAndrieux: Выбор слов-придирка ... эти инструменты обычно мощные и грубые ...: Я на самом деле считаю их очень острыми хирургическими инструментами: вы можете получить именно то, что хотите, как вы этого хотите. Но для наложения швов или очистки хирургических процедур требуются аналогичные навыки и материалы, пластырь не годится. - person Willem van Rumpt; 18.05.2021
comment
@WillemvanRumpt Вы, наверное, правы, аналогия с хирургией, вероятно, лучше. Я выбрал тупой, потому что вы можете работать с затупленным инструментом, но вам, вероятно, придется приложить гораздо больше усилий для выполнения этой задачи. - person François Andrieux; 18.05.2021
comment
Это необходимо не только для деструкторов, но и для освобождения памяти в системах, где размер блока памяти не известен распределителю и должен быть указан при освобождении. Например, на AmigaOS разрешено делать char *p = AllocMem(64); FreeMem(p + 16, 32);, это оставляет вам два распределения по 16 байт каждое, в p и p+48. В AmigaOS delete o; вызывает FreeMem(o, sizeof *o);, поэтому при удалении массива без удаления массива будет освобожден только первый элемент. - person Simon Richter; 18.05.2021
comment
Тот факт, что delete pointer и delete [] pointer используют разные функции освобождения, не имеет ничего общего с первым, имеющим дело с потенциально полиморфным удалением, или вторым, требующим восстановления количества удаляемых элементов. К тому времени, когда вы дойдете до функции освобождения памяти, с этим уже все будет сделано. Я не знаю какой-либо рациональной причины, по которой они использовали отдельный набор функций распределения и освобождения. - person Deduplicator; 18.05.2021
comment
@Deduplicator Я * не совсем уверен в том, почему существуют разные функции освобождения для delete и delete[], мне не ясно, будет ли невозможно иметь одну версию, которая могла бы обрабатывать оба случая без накладных расходов. Я уберу эту часть вопроса из своего ответа. Части о деструкторах должно быть достаточно, чтобы оправдать разницу между delete и delete[]. Изменить: не забыл * - person François Andrieux; 18.05.2021
comment
@Barmar: Вы правы насчет «любопытной микрооптимизации». AFAICT, со стандартами компилятора C ++ было бы вполне допустимо реализовать new X то же самое, что и new X[1], и delete то же самое, что и delete[], таким образом прощая случайное использование неправильного оператора удаления. Но это означает «тратить» память на хранение длины массива. - person dan04; 18.05.2021
comment
@ dan04 Стандарт требует, чтобы новое выражение без массива, создающее T (new T;), требовалось для запроса ровно sizeof(T) байтов от функции распределения. Из-за этого правила вы можете реализовать new T; как new T[1]; только в том случае, если новое выражение с типом массива также не имеет накладных расходов на выделение массива на этой платформе. См.: Этот аргумент должен быть не меньше размера создаваемого объекта; он может быть больше размера создаваемого объекта, только если объект является массивом. - person François Andrieux; 18.05.2021
comment
@Deduplicator Если задуматься, есть еще одно ограничение. Поскольку возможно заменить operator new отдельно от operator new[], даже это может оказаться невозможным. - person François Andrieux; 18.05.2021
comment
@ FrançoisAndrieux Да. Иметь эти две отдельные функции там очень неудобно. - person Deduplicator; 18.05.2021
comment
Да, как только они приняли это первоначальное дизайнерское решение, у него были разветвления, которые повлияли на спецификацию. - person Barmar; 19.05.2021
comment
Обратите внимание, что иногда вам нужно соединить new T() с delete[]. скалярно выглядящий новый отлично способен выполнять выделение массива, если фактическим типом является массив. - person Ben Voigt; 19.05.2021
comment
Ответ часто заключается в использовании какой-то странной функции @awiebe, вероятно, из-под C ++ 11, потому что заметная часть стандартной библиотеки предназначена для предоставления оберток вокруг функций основного языка, чтобы компилятор мог делать за вас тяжелую работу всякий раз, когда вы не нужно что-то оптимизировать до абсолютного предела. Потенциальное небольшое снижение скорости и / или увеличение размера исполняемого файла можно обменять на читаемый код, меньшую бухгалтерию с вашей стороны и лучшие гарантии безопасности. Например, std::vector - это, по сути, просто красивая оболочка, которая управляет new[] и delete[] за вас с дополнительными функциями. - person Justin Time - Reinstate Monica; 19.05.2021
comment
@ FrançoisAndrieux: C ++ дает вам инструменты для работы, максимально приближенные к аппаратному обеспечению. - за исключением случаев, когда комитет решил этого не делать. Обратите внимание, что в C ++ new / delete отсутствует какой-либо механизм C realloc (или try_realloc для нетривиально копируемых), который может расширить существующее выделение, чтобы избежать копирования, когда это возможно. Большинство реальных ОС могут это делать, и, если нетривиальная реализация, просто new / copy / delete или возврат false для try_realloc. Это заставляет большой std::vector тратить много времени, пропускной способности памяти и отказов страниц, копируя материал по мере его роста, если вы не зарезервируете его заранее. - person Peter Cordes; 19.05.2021
comment
Бьярн давно сказал, что [] было ошибкой. - person philipxy; 19.05.2021
comment
@BenVoigt Есть ли футболка Keep C ++ странной? - person Peter - Reinstate Monica; 20.05.2021
comment
@philipxy: Требуется ссылка. - person Ben Voigt; 20.05.2021

Обычно malloc и free выделяют память, а new и delete создают и уничтожают объекты. Итак, вы должны знать, что это за объекты.

Чтобы подробнее рассказать о неуказанных накладных расходах, упомянутых в ответе Франсуа Андриё, вы можете увидеть мой ответ на этот вопрос, в котором я изучил, что делает конкретная реализация делать (Visual C ++ 2013, 32-бит). Другие реализации могут делать то же самое, а могут и не делать.

В случае, если new[] использовался с массивом объектов с нетривиальным деструктором, он выделял на 4 байта больше и возвращал указатель, сдвинутый на 4 байта вперед, поэтому, когда delete[] хочет знать, сколько там объектов, он берет указатель, сдвигает его на 4 байта раньше, берет число по этому адресу и обрабатывает его как количество сохраненных там объектов. Затем он вызывает деструктор для каждого объекта (размер объекта известен из типа переданного указателя). Затем, чтобы освободить точный адрес, он передает адрес, который был на 4 байта раньше переданного адреса.

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

person milleniumbug    schedule 18.05.2021

Что-то не упомянутое в других (вполне хороших) ответах заключается в том, что основная причина этого заключается в том, что массивы, унаследованные от C, никогда не были первоклассной вещью в C ++.

Они имеют примитивную семантику C и не имеют семантики C ++, и, следовательно, компилятор C ++ и поддержка времени выполнения, что позволит вам или системам времени выполнения компилятора делать полезные вещи с указателями на них.

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

Эта бородавка на языке C ++ возникла из-за наследия C. И он остается частью языка - даже несмотря на то, что теперь у нас есть std::array для массивов фиксированной длины и (всегда было) std::vector для массивов переменной длины - в основном для целей совместимость: возможность обращаться с C ++ к API операционной системы и к библиотекам, написанным на других языках, с использованием взаимодействия с языком C.

И ... потому что существует масса книг, веб-сайтов и классов, обучающих массивам очень рано в своей педагогике на C ++, из-за а) способности рано писать полезные / интересные примеры, которые на самом деле действительно вызывать API ОС, и, конечно же, из-за потрясающей силы b) мы всегда так поступали.

person davidbak    schedule 18.05.2021
comment
В этом ответе содержится ряд совершенно неверных утверждений, очевидно, из-за незнания того, что и C, и C ++ поддерживают типы указателей на массив. Дело не в отсутствии возможности выразить указатель на массив, а в неиспользовании этой возможности на практике. - person Ben Voigt; 19.05.2021
comment
указатель на массив мгновенно превращается в указатель на элемент, и именно так он используется. Сколько сигнатур функций / методов C ++ (или C) принимают тип указателя на массив? Никто, но никто не учит этому, и не то, как это используется. Вы не согласны? Например, покажите мне, где в API Unix / Linux указатель на массив используется в сигнатуре функции над голым указателем, который в документации предполагается, что это массив? @BenVoigt - person davidbak; 19.05.2021
comment
@BenVoigt - Ни Язык программирования C (Керниган, Ричи, 1978), ни C: Справочное руководство - 3-е издание (Харбисон, Стил, 1991) (включая как ANSI, так и традиционный C) обсуждают указатель на массив - они только обсуждают, как массив T и указатель на T почти эквивалентны. Когда идентификатор массива впервые появляется в выражении, тип идентификатора преобразуется из массива T в указатель на T, а значение идентификатора преобразуется в указатель на первый элемент массива. (Харбисон, стр. 111). И много других подобных ссылок. - person davidbak; 19.05.2021
comment
Дизайн и развитие C ++ (Страуструп, 1994) не только не упоминает указатель на массив, но и перекликается с формулировкой Харбисона / Стила. - person davidbak; 19.05.2021
comment
Ни в Effective C ++ - 3-е изд. (Meyers, 2008), ни в More Effective C ++ (Meyers, 1996) не упоминаются типы указателей на массив. Я мог бы продолжать читать книги из моей библиотеки, но ... мне все равно. Дело не в том, обладали ли когда-нибудь - даже изначально - языки такой способностью. Дело в том, что этим никто никогда не пользовался. Всегда. Тот факт, что я не упомянул об этом в своем ответе, не означает, что я об этом не знал. Просто я знаю, что это бесполезный остаток накопленных знаний составителя компилятора. Его никогда не использовали, никогда не учили. - person davidbak; 19.05.2021
comment
Основная проблема здесь в том, что типы указателей на массив и ссылки на массив действительно трудно читать, поэтому люди привыкли не использовать их, что приводит к тому, что знания уходят на второй план. . Самый простой способ работать с ними - использовать шаблоны или decltype, и их использование обычно быстро превращается в почти нечитаемый беспорядок . create() здесь достаточно плохо (по-разному), просто представьте себе функцию, которая принимает указатели на два массива и возвращает указатель на массив другого типа. - person Justin Time - Reinstate Monica; 19.05.2021
comment
Поскольку обычное использование new [] - это выделение массива неизвестного размера во время компиляции, указатель на массив C в любом случае не очень помогает. - person Jack Aidley; 19.05.2021
comment
@JackAidley: Это подходит для 2D-массивов, потому что C99 также поддерживает VLA. Другой способ привязки 2D-массива? показывает, что int (*p)[2] = malloc(3 * sizeof *p); - это способ C динамически выделять объект int [3][2] массива массивов, с местным указателем на это. 2 и 3 могут быть переменными времени выполнения в C, в отличие от C ++. - person Peter Cordes; 19.05.2021
comment
@davidbak: Действительно, это выбор между использованием p[INDEX2D(i,j)] для имитации 2D-индексации в плоском массиве или использованием p[i][j] и масштабированием компилятора i по другому измерению массива на основе типа указателя на VLA. Любой из них подходит, если вы избегаете int **p с отдельными выделениями для каждой строки. (nvm, я вижу, вы удалили комментарий, на который я отвечал. Я тоже могу удалить его.) - person Peter Cordes; 19.05.2021
comment
@PeterCordes - w.r.t. Однако в этом конкретном вопросе VLA относятся только к C и были стандартизированы только в C99 - задолго до создания C ++. Википедия по VLA, к сожалению, испытывает недостаток в том, когда GCC впервые их поддерживал, но сомнительно, что Страуструп вообще использовал бы их в качестве модели, поскольку он нестандартен, поскольку его первая реализация на C ++, cfront, была переводчиком на C, и он, вероятно, даже тогда были идеи не привязывать его к конкретному компилятору C. - person davidbak; 19.05.2021
comment
@PeterCordes - я удалил свой комментарий, потому что ... ваша точка зрения была правильным ответом на существующий комментарий ... хотя я сам сомневался в удобочитаемости конструкции. - person davidbak; 19.05.2021
comment
Да, Джек отвечал @JustinTime, который указал, что синтаксис указателя на массив довольно трудно читать. С моей стороны нет аргументов, и обычно их не стоит беспокоить. Постоянное отсутствие поддержки ISO C ++ для VLA означает, что часто бесполезно использовать указатели на массивы в переносимом C ++; std :: array такой же мощный и чище. (GNU C ++ действительно поддерживает VLA в C ++.) Но C ++ наверняка может иметь указатели на массивы, поэтому я согласен с первоначальным комментарием Бена. Единственный тип массива, который он вообще поддерживает, имеет размер, постоянный во время компиляции, но на них можно указать. - person Peter Cordes; 19.05.2021
comment
@petercordes C99 появился немного позже для C ++ - person Jack Aidley; 19.05.2021
comment
@JackAidley: Верно, первоначальный дизайн C ++ был ограничен желанием компилировать на переносимый C в то время, без необходимости полагаться на функции VLA, которые поддерживались некоторыми, но не всеми компиляторами C в то время. Одно это не объясняет, почему в современном C ++ он все еще отсутствует (причины, возможно, связаны с усложнениями деструкторов). Я думал, что мы только что говорили о том, почему синтаксис указателя на массив не широко используется (в C или C ++), и я указал, что для него есть варианты использования C. Я не пытаюсь напрямую спорить о дизайне C ++ (кроме того, что он поддерживает указатель на массив) - person Peter Cordes; 19.05.2021
comment
@PeterCordes - я думаю, причина того, что современный C ++ до сих пор его не хватает, заключается в том, что не стоит прилагать никаких усилий для выполнения каких-либо операций с языком - основных или второстепенных - на столь позднем этапе, чтобы заставить массивы в стиле C работать лучше. Намного больше хлопка, чтобы просто дать нам произвольный ранг operator[], и тогда любой может использовать встроенные проверенные возможности классов и шаблонов C ++ для создания любого чертового массива, который он хочет. Тогда операторы new и delete - как скалярная версия, так и версия массива - будут когда-либо видны инкапсулированными и скрытыми только в одном или двух методах низкоуровневых классов, требующих кучи. - person davidbak; 19.05.2021
comment
Синтаксис VLA намного чище, чем alloca (который также является нестандартным IIRC), когда вам нужен небольшой локальный временный массив в автоматическом хранилище с размером переменной времени выполнения, который, как вы знаете, достаточно мал. (т.е. в стеке в обычной реализации C ++). Что касается динамического распределения, убедитесь, что вы можете просто заключить new[] в класс с перегруженным оператором []. Похоже, что написание массивной оболочки для alloca, когда GNU C ++ поддерживает alignas(32) float foo[n]; для небольшого рабочего места, кажется потраченным впустую. - person Peter Cordes; 19.05.2021
comment
@PeterCordes - интересный вариант использования, я определенно использовал alloca() в прошлом, но мне интересно, будет ли это хорошо - или даже приемлемым - в будущем, когда у нас есть сопрограммы. .. (включая VLA в это) - person davidbak; 19.05.2021
comment
Из en.cppreference.com/w/cpp/language/coroutines он похоже, что дизайн совместим с вызовом из функций, использующих VLA / alloca. Пространство хранения для состояния сопрограммы выделяется в куче с помощью new, или оно может быть оптимизировано (с использованием пространства стека), если позволяет время жизни, и если размер кадра сопрограммы известен на сайте вызова. Компиляторы вполне могут запретить alloca / VLA внутри функций сопрограммы (им, вероятно, нужно, чтобы размер кадра стека был фиксированным на время работы сопрограммы), но стандарт предоставляет определенный способ определить, является ли функция сопрограммой. - person Peter Cordes; 19.05.2021
comment
Есть причины, по которым C ++ не принял его, но они не имеют отношения к выбору delete [] или delete. Это было сделано очень давно. Как бы то ни было, Бьярн признал это ошибкой, IIRC. - person Jack Aidley; 19.05.2021
comment
@davidbak Очевидно, вы об этом не знаете, потому что ошибочно полагаете, что указатель на массив распадается на указатель на первый элемент. Это не. Массив распадается до указателя на начальный элемент, указатель на массив взаимно преобразовывается с указателем на первый элемент, но не распадается. И очень плохо, если в любом учебнике не рассматривается указатель на массив, потому что, в отличие от вашего, он никогда не использовался, на самом деле он довольно распространен. Двумерный массив распадается на указатель на массив. Индексирование в двумерный массив выполняет арифметические действия с указателем на указатель на массив. - person Ben Voigt; 20.05.2021

Как правило, компиляторы C ++ и связанные с ними среды выполнения строятся поверх среды выполнения C платформы. В частности, в этом случае диспетчер памяти C.

Диспетчер памяти C позволяет вам освобождать блок памяти, не зная его размера, но нет стандартного способа получить размер блока из среды выполнения, и нет гарантии, что фактически выделенный блок имеет именно тот размер, который вы просил. Вполне может быть больше.

Таким образом, размер блока, хранящийся в диспетчере памяти C, нельзя использовать для обеспечения функциональности более высокого уровня. Если функциональным возможностям более высокого уровня требуется информация о размере распределения, они должны хранить ее сами. (И C ++ delete[] действительно нуждается в этом для типов с деструкторами, чтобы запускать их для каждого элемента.)

C ++ также придерживается позиции, согласно которой вы платите только за то, что используете, хранение поля дополнительной длины для каждого выделения (отдельно от бухгалтерии базового распределителя) не соответствовало бы такой позиции.

Поскольку обычным способом представления массива неизвестного (во время компиляции) размера в C и C ++ является указатель на его первый элемент, компилятор не может отличить выделение одного объекта от выделения массива на основе типа. система. Так что возможность различать остается на усмотрение программиста.

person plugwash    schedule 19.05.2021

Прикрытие состоит в том, что delete требуется из-за связи C ++ с C.

Оператор new может создать динамически выделяемый объект практически любого типа объекта.

Но из-за наследия C указатель на тип объекта неоднозначен между двумя абстракциями:

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

Ситуация delete против delete[] просто следует из этого.

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

Вот неофициальное доказательство. Вызов оператора new T (случай одного объекта) мог неявно вести себя так, как если бы он был new T[1]. То есть каждый new всегда может выделить массив. Когда не упоминается синтаксис массива, может подразумеваться, что будет выделен массив из [1]. Тогда просто должна была бы существовать одна delete, которая ведет себя как сегодняшняя delete[].

Почему не соблюдается этот дизайн?

Думаю, все сводится к обычному: это коза, принесенная в жертву богам эффективности. Когда вы выделяете массив с помощью new [], для метаданных выделяется дополнительная память, чтобы отслеживать количество элементов, так что delete [] может знать, сколько элементов необходимо повторить для уничтожения. Когда вы выделяете один объект с помощью new, такие метаданные не требуются. Объект может быть построен непосредственно в памяти, которая поступает из нижележащего распределителя без какого-либо дополнительного заголовка.

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

person Kaz    schedule 20.05.2021

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

Вот пример:

#include <iostream>
#include <stdlib.h>
#include <string>

using std::cerr;
using std::cout;
using std::endl;

class silly_string : private std::string {
  public:
    silly_string(const char* const s) :
      std::string(s) {}
    ~silly_string() {
      cout.flush();
      cerr << "Deleting \"" << *this << "\"."
           << endl;
      // The destructor of the base class is now implicitly invoked.
    }

  friend std::ostream& operator<< ( std::ostream&, const silly_string& );
};

std::ostream& operator<< ( std::ostream& out, const silly_string& s )
{
  return out << static_cast<const std::string>(s);
}

int main()
{
  constexpr size_t nwords = 2;
  silly_string *const words = new silly_string[nwords]{
    "hello,",
    "world!" };

  cout << words[0] << ' '
       << words[1] << '\n';

  delete[] words;

  return EXIT_SUCCESS;
}

Эта тестовая программа явно инструментирует вызовы деструктора. Очевидно, это надуманный пример. Во-первых, программе не нужно освобождать память непосредственно перед завершением работы и освобождением всех своих ресурсов. Но он демонстрирует, что происходит и в каком порядке.

Некоторые компиляторы, такие как clang++, достаточно умны, чтобы предупредить вас, если вы оставите [] в delete[] words;, но если вы все равно заставите его скомпилировать ошибочный код, вы получите повреждение кучи.

person Davislor    schedule 19.05.2021

Delete - это оператор, который уничтожает объекты массива и объекты, не являющиеся массивом (указателем), которые генерируются новым выражением.

Его можно использовать либо с помощью оператора Delete, либо с помощью оператора Delete []. Новый оператор используется для динамического распределения памяти, который помещает переменные в динамическую память. Это означает, что оператор Delete освобождает память из кучи. Указатель на объект не уничтожается, значение или блок памяти, на который указывает указатель, уничтожается. Оператор удаления имеет тип возвращаемого значения void, который не возвращает значения.

person Aditya-ai    schedule 14.06.2021