Почему ::operator new[] необходим, когда ::operator new достаточно?

#c #memory-management #overloading #standards #new-operator

#c #управление памятью #перегрузка #стандарты #new-operator

Вопрос:

Как мы знаем, стандарт C определяет две формы глобальных функций распределения:

 void* operator new(size_t);
void* operator new[](size_t);
  

Кроме того, в проекте стандарта C (18.6.1.2 n3797) говорится:

227) В обязанности operator new или operator delete не входит указывать количество повторений или размер элемента массива. Эти операции выполняются в другом месте массива выражениями new и delete. Выражение array new, однако, может увеличить аргумент size до operator new, чтобы получить место для хранения дополнительной информации.

Что меня смущает, так это:

Что, если мы удалим void* operator new[](size_t); из стандарта и просто используем void* operator new(size_t) вместо этого? В чем смысл определения избыточной глобальной функции распределения?

Комментарии:

1. Интересный вопрос. Обычно при добавлении поддержки C в систему, использующую только C, operator new и operator new[] оба просто разворачиваются и вызывают malloc .

2. Просто размышляю здесь. Но на самом деле это допустимое различие в случае перегрузки этих операторов для типа класса, поэтому, возможно, непротиворечиво, чтобы глобальные версии имели одинаковый интерфейс.

3. Примечание странно в том смысле, что, по-видимому, нет причин, по которым версия без массива также могла бы этого не делать increase the size argument to operator new to obtain space to store supplemental information , хотя никаких замечаний на этот счет не сделано.

4. @Keith: Я вспомнил, что читал где-то в Стандарте, что это специально запрещено — нашел это — «это может быть больше размера создаваемого объекта, только если объект является массивом»

5.@Keith: вы сказали «предположительно, нет причин, по которым версия без массива также не могла бы увеличить аргумент size до operator new, чтобы получить место для хранения дополнительной информации» — очевидно, вы говорили о new[] выражении, поскольку именно там в Стандарте указано, что увеличение разрешено — я просто говорю, что эквиваленту без массива не разрешено это делать. Если вы хотите предположить, что это может произойти в другом месте, это прекрасно, но отличается.

Ответ №1:

Я думаю, ::operator new[] это могло быть полезно для довольно специализированных систем, где «большие, но немногочисленные» массивы могут быть выделены другим распределителем, чем «маленькие, но многочисленные» объекты. Однако в настоящее время это что-то вроде пережитка.

operator new можно разумно ожидать, что объект будет создан по точному возвращенному адресу, но operator new[] не может. Первые байты блока выделения могут использоваться для размера «cookie», массив может быть редко инициализирован и т.д. Различие становится более значимым для member operator new , который может быть специализирован для своего конкретного класса.

В любом случае, ::operator new[] не может быть очень важным, потому что std::vector (через std::allocator ), который в настоящее время является наиболее популярным способом получения динамических массивов, игнорирует его.

В современном C пользовательские распределители обычно являются лучшим выбором, чем настраиваемые operator new . На самом деле, new выражений следует полностью избегать в пользу классов container (или smart-pointer и т.д.), Которые обеспечивают большую безопасность исключений.

Комментарии:

1. Я шокирован, говорю я, шокирован и встревожен тем, что std::vector не использует new[] ! Хммм.

2. @einpoklum: vector требует, чтобы новости размещения работали, а размещение new с массивами — это кошмар, и пытаться использовать new[] несколько раз в предварительно выделенном буфере, как требует vector, совершенно невозможно сделать переносимо.

3. @david.pfx Какая часть этого неверна? Вы неопределенно упоминаете «специализированное индексирование массивов» и «CDC, IBM и т.д.», Но это далеко от конкретного утверждения, и мое самое первое предложение охватывает это основание. (Я не упоминал пулы памяти, я сказал «выделенные другим распределителем», и это касается учета архитектурных особенностей.) В вашем ответе следует уточнить «Дополнительное пространство для индексации массива»; требования могут быть более строгими, чем вы думаете.

4. @einpoklum Если вы хотите, вы можете создать свой собственный распределитель, который идентичен std::allocator за исключением вызова ::operator new[] вместо ::operator new (и соответствующего delete ). Но, вероятно, ничего не получится, поскольку каждая реализация уже оптимизирована для значения по умолчанию std::vector , которое выделяет массивы с использованием функций, отличных от массива.

Ответ №2:

::operator new[] и ~ delete[] облегчают отладку использования памяти, являясь центральным пунктом аудита операций выделения и освобождения; затем вы можете убедиться, что форма массива используется для обоих или ни для чего другого.

Существует также множество правдоподобных, хотя и крайне необычных / грубых способов настройки:

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

  • различные подсказки доступа к памяти (ala madvise ) для массивных / не массивных данных

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

Комментарии:

1. @MooingDuck: внимательно обратите внимание на начальную букву «~»… O_o.

2. Я подумал, что это опечатка, я никогда не сталкивался с таким синтаксисом, кроме как с именами псевдодеструкторов.

3. @MooingDuck: это сокращение в общепринятом английском языке, а не нотация C , для обозначения того, что префикс некоторой предыдущей записи появляется в том же месте, часто используется в словарных статьях; например: проблема <описание> ~ some <описание неприятного> …

4. … Английский или Perl? … Следуя словарю, я вдохновлен на #define opərator operator

5. @Potatoswatter: выбей себя из колеи… что бы ни прихлопнуло вашу картошку.

Ответ №3:

Стандарт (n3936) ясно дает понять, что эти два оператора служат разным, но взаимосвязанным целям.

operator new вызывает функцию void* operator new(std::size_t) . Первый аргумент должен быть идентичен аргументу оператора. Он возвращает блок памяти, выровненный соответствующим образом и который может быть несколько больше требуемого.

operator new[] вызывает функцию void* operator new[](std::size_t) . Первый аргумент может быть больше аргумента, предоставленного оператору, чтобы обеспечить дополнительное пространство для хранения, если требуется для индексации массива. Реализация по умолчанию для обоих — просто вызвать malloc().

Цель operator new[] — поддерживать специализированную индексацию массива, если таковая имеется. Это не имеет ничего общего с пулами памяти или чем-либо еще. В соответствующей реализации, которая использовала эту функцию, реализация создавала бы специализированные таблицы в дополнительном пространстве, а компилятор генерировал бы код для инструкций или вызовов подпрограмм поддержки библиотеки, которые использовали эти таблицы. Код на C , использующий массивы и не использующий new [], приведет к сбою на этих платформах.

Лично я не знаком с какой-либо подобной реализацией, но она напоминает функции, необходимые для поддержки определенных мэйнфреймов (CDC, IBM и т.д.), Архитектура которых совершенно не похожа на чипы Intel или RISC, которые мы знаем и любим.

На мой взгляд, принятый ответ неверен.


Просто для полноты картины стандарт (n3936 в основном в S5.3.4) содержит следующее.

  1. Различие между выделением «объекта массива» или «объекта, не являющегося массивом»
  2. Ссылки на «накладные расходы на распределение массива», подразумевающие, что может потребоваться дополнительное хранилище, и оно может (каким-то образом) использоваться для подсчета количества повторений или размера элемента.

Нет ссылки на пулы памяти или какого-либо намека на то, что это могло бы быть рассмотрено.

Комментарии:

1. Значит, в этих реализациях std::vector<int, std::allocator<int> > это не сработало бы?

2. @Mehrdad: Они бы сработали. Это оптимизация , которая может быть выполнена компилятором для выделения дополнительного пространства, которое будет использоваться (генерируемым компилятором) специализированным кодом поиска.

3. Трудно сказать, не имея реальной реализации, на которую можно было бы взглянуть.

4. Тогда, я думаю, вы неправильно поняли, о чем эта часть стандарта … многие существующие реализации хранят подсчет количества элементов в массиве с известным смещением от возвращаемого указателя, чтобы delete[] можно было найти это число и вызвать деструктор для соответствующего количества элементов — это объясняет все части стандарта, которые вы упоминаете. Ваши «специализированное индексирование массива» и «специализированные таблицы» заставляют это звучать как некоторая фрагментация массива в виртуальном адресном пространстве между сегментами памяти, что представляет собой совершенно иную перспективу, на которую Стандарт не ссылается.

5. @david.pfx: Мне не нужно ваше благословение, чтобы комментировать ваш ответ… вы находитесь на публичном форуме, где в порядке вещей запросы о разъяснениях / ссылках, вежливое предложение о том, что возможна другая интерпретация доказательств, на которых, по вашим словам, вы основывали свои выводы, и дружеское упоминание о том, что ваш ответ может быть неверно истолкован. Это частично предлагается для людей, читающих ваш ответ, которые хотят знать, что другие люди думают об этом. Вы решили явно отклонить мой ответ в своем (где читатели могут его не увидеть), а не комментировать — каждый свой.

Ответ №4:

Я уверен, что существуют подходящие варианты использования, требующие разделения new[] и new , но я еще не встречал ни одного, который был бы однозначно возможен при таком разделении и ни при чем другом.

Однако я вижу это так: поскольку пользователь вызывает разные версии operator new , стандарт C был бы виновен в бессмысленной и намеренной потере информации, если бы они определили только одну operator new и передали туда обе new и new[] forward . Здесь есть (буквально) один бит информации, который мог бы кому-нибудь пригодиться, и я не думаю, что люди в комитете могли бы с чистой совестью выбросить его!

Кроме того, необходимость реализации дополнительного new[] является очень-очень незначительным неудобством для остальных из нас, если вообще возникает, поэтому компромисс с сохранением одного бита информации выигрывает от необходимости реализации одной простой функции в небольшой части наших программ.

Ответ №5:

В C Programming Language: Special Edition на стр. 423 говорится

_ Функции operator new() и operator delete() operator new[]() и operator delete[]() выполняют точно такую же роль для выделения и освобождения массивов. Функции позволяют пользователю управлять распределением и освобождением отдельных объектов;,, и ,, выполняют точно такую же роль для выделения и освобождения массивов.

Спасибо Тони Ди за исправление моего непонимания этого нюанса.

Вау, не часто меня застает что-то в C , в чем я так уверен — должно быть, я трачу слишком много времени на Objective-C!

изначально неправильный ответ

Все просто — форма new[] вызывает конструктор для каждого элемента классического массива C.

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

Комментарии:

1. Вы думаете о new expression , вместо operator new . Немного отличается.

2. Я смущен вашим ответом. Я думал, что operator new использовался для определения эффектов, предоставляемых с помощью нового выражения. Вы хотите сказать, что ссылка на cplusplus.com тоже неправильно?

3. @AndyDent: часто так и есть (я предпочитаю cppreference или сам стандарт), но в данном случае это правильно: «Выражение с оператором new для типа массива сначала вызывает функцию operator new (т. Е. Эту функцию) […], и если это выполняется успешно, оно затем автоматически инициализирует или создает каждый объект в массиве (при необходимости)». Обратите внимание, как это говорит о том, что это new выражение отвечает за вызов оператора, а затем за выполнение конструкции, которую вы описываете….

4. отредактированный ответ, соответствующий пониманию Тони после проверки Библии