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

Я чертовски уверен, что этот код должен быть незаконным, поскольку он явно не будет работать, но, похоже, он разрешен C++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's 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's answer
delete [] p; // legal?  I hope not

Это самый близкий вопрос, который мне удалось найти .

РЕДАКТИРОВАТЬ: я просто не согласен с тем, что аргументы стандартного языка, ограничивающие аргументы функции 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.


person Ben Voigt    schedule 11.12.2010    source источник
comment
К вашему сведению: FCD (N3092) больше не является последним проектом. Последний проект - N3225. Я обновлял вики-страницу тега c++-0x со ссылкой на последний черновик PDF.   -  person James McNellis    schedule 11.12.2010
comment
Обратите внимание, что 5.3.5/2, который охватывает это, был изменен в последнем проекте. Теперь он говорит, что указатель может быть указателем на объект, не являющийся массивом, созданный предыдущим new-expression, а new-expression действительно включает новые выражения размещения. Я не думаю, что это предназначено.   -  person James McNellis    schedule 11.12.2010
comment
@James: Большое спасибо за новый черновик. И 5.3.5 - это именно тот раздел, который, как я думаю, должен запрещать это, но это не так. Не могли бы вы взглянуть на мой ответ (я готов вытащить любой измененный язык из нового проекта) и сообщить мне, если вы считаете, что это имеет какое-либо отношение к этому вопросу?   -  person Ben Voigt    schedule 11.12.2010
comment
@James: отличная страница по C++0x, и спасибо за последний черновик, у меня нет прав на его редактирование (без бронзового значка C++0x: p), как вы думаете, можно ли добавить Clang C++0x статус. Реализация только начинается (до сих пор они были сосредоточены на совместимости с C++03), но уже реализована пара функций. Вот ссылка: clang.llvm.org/cxx_status.html   -  person Matthieu M.    schedule 11.12.2010
comment
@ Джеймс, не могли бы вы добавить свой комментарий в ответ, чтобы я мог его принять?   -  person Ben Voigt    schedule 19.12.2010


Ответы (4)


Стандартные правила на [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*) библиотек, если только вы не перезаписали его. Поскольку вы ничего не сказали об этом, я предполагаю, что вы этого не сделали.

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

person Johannes Schaub - litb    schedule 11.12.2010
comment
Это, безусловно, относится к вызову функций освобождения памяти напрямую с использованием синтаксиса вызова функции. Но я не думаю, что это сразу применимо к вызовам operator delete, поскольку указатель, который я использую в качестве операнда operator delete, не обязательно является тем, который в конечном итоге передается функции освобождения. Ну, читая 5.3.4 [expr.new] более внимательно, это то же самое для скалярного new. Поэтому подумайте о размещении нового массива вместо этого, а затем о удалении массива. - person Ben Voigt; 11.12.2010
comment
@ Бен, я не понимаю твоего комментария. - person Johannes Schaub - litb; 11.12.2010
comment
Предполагается, что компилятор отвечает за соблюдение правил для функций освобождения, пока я следую правилам для new-expression и delete-expression. Что, к сожалению, делает этот код, согласно текущей формулировке, цитируемой Джеймсом. - person Ben Voigt; 11.12.2010
comment
Я добавил к фрагменту кода вопроса вызов, который ваша цитата явно делает незаконным, для сравнения. - person Ben Voigt; 11.12.2010
comment
@Ben нет, потому что вы передаете указатель на delete, который, в свою очередь, вызывает operator delete с этим указателем (или с указателем, настроенным на постоянный коэффициент), что, в свою очередь, вызывает неопределенное поведение. Значит, вы не играете по правилам. - person Johannes Schaub - litb; 11.12.2010
comment
Хорошо, я добавлю версию массива, где эта цепочка рассуждений не выдерживает критики. - person Ben Voigt; 11.12.2010
comment
@Ben та же проблема с версией массива. p или p с поправкой на постоянный член не были гарантированно возвращены одной из перечисленных operator new[] функций, поэтому поведение не определено. - person Johannes Schaub - litb; 11.12.2010
comment
@Ben, текст, который вы цитируете, не касается требования о том, что operator delete(void*) не может передаваться указатель, не возвращаемый одной из перечисленных версий operator new. - person Johannes Schaub - litb; 11.12.2010
comment
Я согласен с тем, что код полностью сломан и никогда не сможет работать. Я просто думаю, что есть дефект, потому что стандарт обещает заставить его работать, даже если это невозможно (за исключением того, что компилятор волшебным образом выясняет, как вызвать версию размещения ::operator delete(void*), которая не является операцией, или снова передает NULL в функцию освобождения для этого требуется знание магии). - person Ben Voigt; 11.12.2010
comment
@Johannes: Где в стандарте вы получаете p или p с поправкой на постоянный член? Это может быть то, что большинство практических реализаций делают во время удаления массива или при отсутствии виртуального деструктора, но это не гарантируется стандартом, который я вижу, и даже не является правильным в общий случай. - person Ben Voigt; 13.12.2010

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

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

person Frédéric Hamidi    schedule 11.12.2010

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

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

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

person dmckee --- ex-moderator kitten    schedule 11.12.2010
comment
Это определенно небезопасно, потому что это выражение удаления требуется для вызова функции освобождения, передавая указатель, не полученный от соответствующей функции распределения. - person Ben Voigt; 11.12.2010

Я нашел это в разделе библиотеки стандарта, который настолько нелогичен, насколько это возможно (IMO):

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

Эти функции зарезервированы, программа на C++ не может определять функции, замещающие версии в стандартной библиотеке C++ (17.6.3). Положения (3.7.4) не применяются к этим зарезервированным формам размещения оператора new и оператора 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.

person Ben Voigt    schedule 11.12.2010