Легальность использования оператора 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
.
Ответы
Ответ 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.