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

Легальность использования оператора delete на указателе, полученном из размещения new

Я уверен, что этот код должен быть незаконным, поскольку он явно не работает, но, похоже, он разрешен С++ 0x FCD.

class X { /* ... */};
void* raw = malloc(sizeof (X));
X* p = new (raw) X(); // according to the standard, the RHS is a placement-new expression
::operator delete(p); // definitely wrong, per litb answer
delete p; // legal?  I hope not

Возможно, один из вас, юристов, может объяснить, как стандарт запрещает это.

Также существует форма массива:

class X { /* ... */};
void* raw = malloc(sizeof (X));
X* p = new (raw) X[1]; // according to the standard, the RHS is a placement-new expression
::operator delete[](p); // definitely wrong, per litb answer
delete [] p; // legal?  I hope not

Это самый близкий вопрос, который я смог найти.

EDIT: я просто не покупаю аргумент о том, что стандартный язык, ограничивающий аргументы для функции void ::operator delete(void*), применим любым существенным способом к операнду delete в выражении удаления. В лучшем случае связь между ними крайне незначительна, и несколько выражений допускаются в качестве операндов до delete, которые недействительны для перехода на void ::operator delete(void*). Например:

struct A
{
  virtual ~A() {}
};

struct B1 : virtual A {};

struct B2 : virtual A {};

struct B3 : virtual A {};

struct D : virtual B1, virtual B2, virtual B3 {};

struct E : virtual B3, virtual D {};

int main( void )
{
  B3* p = new E();
  void* raw = malloc(sizeof (D));
  B3* p2 = new (raw) D();

  ::operator delete(p); // definitely UB
  delete p; // definitely legal

  ::operator delete(p2); // definitely UB
  delete p2; // ???

  return 0;
}

Надеюсь, это показывает, что если указатель может быть передан на void operator delete(void*), не имеет никакого отношения к тому, может ли этот же указатель использоваться в качестве операнда delete.

4b9b3361

Ответ 1

Стандартные правила в [basic.stc.dynamic.deallocation] p3

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

Ваш вызов delete вызовет библиотеки operator delete(void*), если вы не перезаписали его. Поскольку вы ничего не сказали об этом, я предполагаю, что вы этого не сделали.

"Должно" выше должно быть что-то вроде "поведение undefined если нет", поэтому оно не ошибочно считается диагностическим правилом, которое не является [lib.res.on.arguments] p1. Это было исправлено с помощью n3225, поэтому его больше нельзя ошибаться.

Ответ 2

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

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

Ответ 3

Я полагаю, вы можете уйти от него (в конкретном компиляторе), если

  • new/delete реализованы в терминах malloc/free и
  • размещение new фактически использует тот же механизм для отслеживания деструктора, связанного с выделениями, как стандартный new, так что вызов delete мог найти правильный деструктор.

Но он не может быть переносимым или безопасным.

Ответ 4

Я нашел это в разделе библиотеки стандарта, которое, по возможности, является скорее контр-интуитивным местоположением (IMO):

С++ 0x FCD (и n3225 draft) раздел 18.6.1.3, [new.delete.placement]:

Эти функции зарезервированы, С++ программа не может определять функции, которые вытеснить версии в стандарте Библиотека С++ (17.6.3). Положения из (3.7.4) не относятся к этим зарезервированные формы размещения оператора новый и оператор delete.

void*  operator  new(std::size_t  size,  void*  ptr)  throw();
void*  operator  new[](std::size_t  size,  void*  ptr)  throw();
void  operator  delete(void*  ptr,  void*)  throw();
void  operator  delete[](void*  ptr,  void*)  throw();

Тем не менее, раздел, определяющий юридические выражения для перехода на delete, равен 5.3.5, а не 3.7.4.