Подтвердить что ты не робот

Смешивание оператора new [] и размещение нового с обычным удалением []

Просто из любопытства, является ли законным?

X* p = static_cast<X*>(operator new[](3 * sizeof(X)));
new(p + 0) X();
new(p + 1) X();
new(p + 2) X();

delete[] p;   // Am I allowed to use delete[] here? Or is it undefined behavior?

Аналогично:

X* q = new X[3]();

(q + 2)->~X();
(q + 1)->~X();
(q + 0)->~X();
operator delete[](q);
4b9b3361

Ответ 1

Я уверен, что оба дают UB.

В §5.3.4/12 говорится, что форма массива нового выражения может добавить некоторое количество накладных расходов в объем выделенной памяти. Затем удаление массива может/может сделать что-то с дополнительной памятью, которую он ожидает, но не потому, что вы не выделили лишнее пространство, которое оно ожидает. По крайней мере, он, как правило, будет, по крайней мере, компенсировать объем дополнительной памяти, который, как ожидается, будет выделен, чтобы вернуться к адресу, который, по его мнению, был возвращен с operator new, но поскольку вы не выделили дополнительную память или не применили offset, когда он сделает с ним, будет передан указатель на operator delete[], который не был возвращен из operator new[], что приведет к UB (и, фактически, даже попытка сформировать адрес до начала возвращаемого адреса технически УБ).

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

§5.3.4/12 является ненормативной запиской, но я не вижу в нормативном тексте ничего не противоречащего.

Ответ 2

Из 5.3.5 [expr.delete] в n3242:

2

[...]

Во втором варианте (delete массив), значение операнда delete может быть значением нулевого указателя или значение указателя, которое было предыдущий массив new-expression. Если не, поведение undefined. [...]

что означает, что для delete[] p, p должно было быть результатом чего-то вида new[] p (новое выражение) или 0. Видя как результат operator new здесь не указано, я думаю, что первый случай сразу.


Я считаю, что второй случай - это хорошо. Из 18.6.1.2 [new.delete.array]:

11

void operator delete[](void* ptr) noexcept;

[...]

Требуется: ptr должен быть нулевым указателем или его значение должно быть значением возвращенный более ранним оператора нового или operator new [] (std:: size_t, const std:: nothrow_t &), который не был недействительным посредством промежуточного оператор удаляет. [...]

(имеется аналогичный текст в 3.7.4.2 [basic.stc.dynamic.deallocation], пункт 3)

Таким образом, пока функции деления/распределения (например, delete[] (new[3] T) хорошо сформированы), ничего плохого не происходит. [или делает это? см. ниже]


Я думаю, что я проследил нормативный текст того, о чем предупреждает Джерри, в 5.3.4 [expr.new]:

10

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

В этом же параграфе приведен пример (так ненормативный), который подчеркивает, что новые выражения реализации действительно могут запрашивать больше от функции распределения, чем пространство, которое занимает массив (сохраняя необязательный параметр std::size_t доступный функции деаллокации), и что они могут компенсировать результат. Таким образом, все ставки отключены в массиве. Случай без массива кажется хорошим, хотя:

auto* p = new T;
// Still icky
p->~T();
operator delete(p);

Ответ 3

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

new-expression    = allocation-function  +  constructor
delete-expression = destructor  +  deallocation-function

Ничего больше, не меньше. Но Стандарт не говорит точно, что, насколько я знаю. Возможно, что new-expression делает больше, чем allocation-function + constructor. То есть фактические уравнения могут быть такими, и стандарт не запрещает это явно где-либо:

new-expression    = allocation-function  +  constructor   +  some-other-work
delete-expression = destructor  +  deallocation-function  +  some-other-work

Ответ 4

Если они не UB, они должны быть. В примере 1 вы используете delete[], где основной механизм не знает, сколько объектов нужно уничтожить. Если в реализации new[] и delete[] используются файлы cookie, это не удастся. Код в примере 2 предполагает, что адрес q является правильным адресом для перехода на operator delete[], и это не относится к реализации, в которой используются файлы cookie.

Ответ 5

Правильно было бы:

X* p = static_cast<X*>(new char[3 * sizeof(X)]);
// ...
delete[] static_cast<char*>(p);

или

X* p = static_cast<X*>(operator new[](3 * sizeof(X)));
// ...
operator delete[](p);

Тип выражения удаления массива должен точно соответствовать новому выражению.


Первый пример - UB, потому что раздел 5.3.5 ([expr.delete]) говорит

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


Моя исправленная версия в порядке, потому что (раздел 3.9 [basic.life]):

Программа может завершить время жизни любого объекта, повторно используя хранилище, которое занимает объект, или явно вызвав деструктор для объекта типа класса с нетривиальным деструктором. Для объекта типа класса с нетривиальным деструктором программа не требует прямого вызова деструктора до того, как хранилище, которое объект занимает, повторно используется или освобождается; однако, если нет явного вызова деструктора или если выражение удаления (5.3.5) не используется для освобождения хранилища, деструктор не должен быть неявно вызван и любая программа, зависящая от побочных эффектов, создаваемых деструктором не определился поведение.


Второй пример не допускается, если if X имеет нетривиальный деструктор, потому что (также 3.9 [basic.life]):

До того, как срок службы объекта запустился, но после хранения, в котором будет заниматься объект, выделено 38 или, после того, как срок жизни объекта закончился и перед хранилищем, которое занимал объект, повторно используемый или выпущенный, любой указатель, который ссылается на место хранения, в котором будет находиться или находился объект могут использоваться, но только ограниченным образом. По строительству или разрушению объекта см. 12.7. В противном случае, такой указатель относится к выделенному хранилищу (3.7.4.2) и использует указатель, как если бы указатель имел тип void*, хорошо определен. Такой указатель может быть разыменован, но полученное значение l может использоваться только в ограниченных способов, как описано ниже.

В программе есть поведение undefined, если:

  • объект будет или имеет тип класса с нетривиальным деструктором, а указатель используется как операнд выражения-удаления,