Может ли вызов удалить указатель, выделенный новым размещением?

Можем ли мы вызвать delete для указателя, выделенного с помощью местоположения new? Если нет, то почему? Пожалуйста, объясните подробно.

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

delete делает две вещи:

  1. Вызывает деструктор
  2. Освобождает память

И я не вижу причин для того, чтобы delete не мог вызвать ни одну из этих двух операций над объектом, который был создан путем размещения new. Есть идеи о причинах?


person Narek    schedule 14.08.2011    source источник
comment
связанный вопрос   -  person fredoverflow    schedule 14.08.2011
comment
Почему вы используете новое размещение? Какую потребность он удовлетворяет?   -  person Jonathan Grynspan    schedule 14.08.2011


Ответы (5)


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

Поскольку каждая разновидность выделения памяти использует некоторую специфичную для реализации отслеживание памяти (обычно блок заголовка, который предшествует адресу пользователя), и это позволяет выделению/освобождению работать только при правильном сочетании:

  • new должен быть в паре с delete
  • new[] должен сочетаться с delete[] (хотя большинство реализаций простят смешение new и new[])
  • malloc и Фрид должны сочетаться с free
  • CoTaskMemAlloc пар с CoTaskMemFree
  • alloca пары ни с чем (об этом позаботится раскрутка стека)
  • MyCustomAllocator пар с MyCustomFree

Попытка вызвать неправильный делокатор приведет к непредсказуемому поведению (скорее всего, ошибка seg сейчас или позже). Поэтому вызов delete в памяти, выделенной чем-то другим, кроме new, приведет к плохим последствиям.

Кроме того, новое место размещения может быть вызвано по любому адресу, который может даже не быть выделенным адресом. Его можно вызвать по адресу, расположенному в середине какого-то более крупного объекта, его можно вызвать по области с отображением памяти, его можно вызвать по необработанной виртуальной фиксированной области, что угодно. delete попытается во всех этих случаях сделать то, что говорит ему его реализация: вычесть размер заголовка, интерпретировать его как заголовок new, связать его обратно с кучей. Кабум.

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

person Remus Rusanu    schedule 14.08.2011
comment
Спасибо, кажется некоторые поняли мой вопрос!!! +1, хотя я хотел бы узнать больше о некоторых конкретных реализациях отслеживания памяти. не могли бы вы подсказать ссылку? - person Narek; 15.08.2011
comment
Вот обсуждение malloc работ на Mac: cocowithlove.com/2010/05/look-at-how-malloc-works-on-mac.html со ссылками на источник. - person Remus Rusanu; 15.08.2011

Вы должны вызывать delete только для указателей, созданных с помощью operator new. Если вы используете размещение new с местом в памяти, которое было выделено обычным operator new, то вы можете безопасно использовать на нем delete (при условии, что вы правильно указали типы и указатели). Однако вы можете использовать размещение new в любой памяти, поэтому обычно вы будете управлять этой памятью каким-то другим способом и вызывать деструктор объекта вручную.

Например, в этом запутанном и обычно ненужном сценарии безопасно delete использовать память, на которой вы использовали размещение new, но только потому, что вы ранее выделили ее с помощью new:

char* mem = new char[sizeof(MyObject)];
MyObject* o = new (mem) MyObject;

// use o

o->~MyObject(); // with placement new you have to call the destructor by yourself

delete[] mem;

Однако это незаконно:

char mem[16]; // create a buffer on the stack, assume sizeof(MyObject) == 16

MyObject* o = new (mem) MyObject; // use stack memory to hold a MyObject
                                  // note that after placement new is done, o == mem
                                  // pretend for this example that the point brought up by Martin in the comments didn't matter

delete o; // you just deleted memory in the stack! This is very bad

Другой способ думать об этом состоит в том, что delete только освобождает память, выделенную ранее обычным new. При размещении new вам не нужно использовать память, которая была выделена обычным new, поэтому с возможностью того, что она не была выделена обычным new, delete не может с ней справиться.

person Seth Carnegie    schedule 14.08.2011
comment
Также обратите внимание, что char mem[16] не гарантируется правильное выравнивание для чего-либо, кроме char. При использовании нового размещения вы всегда должны делать это в динамически выделенной памяти. - person Martin York; 14.08.2011
comment
@Martin, если вы используете массив char, который был динамически выделен, гарантируется ли его правильное выравнивание для всего? - person Seth Carnegie; 14.08.2011
comment
да. С одной оговоркой: выделяемое пространство должно быть не меньше размера объекта, для которого вы хотите выполнить выравнивание. Что обычно не является проблемой, так как в противном случае вы все равно не могли бы использовать его для размещения новых. - person Martin York; 15.08.2011

Нет, так как delete не только вызывает деструктор, но и освобождает память, но если вы использовали новое размещение, вы должны были выделить память самостоятельно, используя malloc() или стек. Однако вы должны сами вызвать деструктор. Также см. часто задаваемые вопросы по C++.

person Antti    schedule 14.08.2011
comment
На самом деле я пришел из этого FAQ :). Там написано, что вы должны вызывать деструктор явно, и я мог понять, почему просто оператор удаления не вызывает деструктор и не освобождает память, если память была выделена путем размещения new. - person Narek; 14.08.2011
comment
Чтобы быть точным, размещение нового не выделяет память, оно только помещает новый объект в память, которая уже была выделена. Поскольку удаление не может сказать, кто и как выделил память, оно не может ее освободить. - person Antti; 14.08.2011
comment
Но зачем детельте знать кто и как выделил память. Его работа - вызвать доктора и освободить память, вот и все! Зачем удалять исторические детали? - person Narek; 15.08.2011
comment
new и delete — это интерфейс распределителя памяти. За кулисами они содержат связанный список фрагментов памяти, которые извлекаются из ОС и передаются пользователю с помощью new, а удаление может освободить только память, которая была выделена с помощью new. Например, он не может освободить память, выделенную в стеке, потому что это не его работа и он ничего об этом не знает. - person Antti; 15.08.2011

Нет. Выражение удаления места размещения отсутствует.

Типичный сценарий:

void * const addr = ::operator new(sizeof(T));  // get some memory

try {
  T * const pT = new (addr) T(args...);    // construct
  /* ... */
  p->~T();                                      // nap time
}
catch (...) {
}
::operator delete(addr);  // deallocate
                          // this is _operator_-delete, not a delete _expression_

Обратите внимание, что у нового оператора размещения есть соответствующий оператор удаления, который обязательно должен быть точно void ::operator delete(void* [, size_t]) { }, без операции; это то, что вызывается, если конструктор T выдает исключение.

person Kerrek SB    schedule 14.08.2011
comment
Я знаю, что нет размещения удаления, но почему просто оператор удаления не может удалить память, не заботясь о том, как распределена та память, на которую указывает указатель? - person Narek; 14.08.2011
comment
@Narek, потому что delete только освобождает память, выделенную new, но с placement new вы можете использовать память, которая не была выделена обычным new, поэтому обычный delete не может позаботиться об этом. тот. - person Seth Carnegie; 14.08.2011
comment
Обычный оператор удаления может удалить только ту память, которая была получена обычным оператором new. Как видите, именно так я использую это в примере. Память, которую вы передаете выражению "placement-new" и, следовательно, оператору "placement-new", берется откуда-то еще и не имеет отношения к оператору "placement-delete". Если подумать, оператор размещения-удаления действительно освобождает память, полученную от оператора размещения-нового, а именно ничего не делая, что именно то, что оператор размещения-нового делал с получить память. - person Kerrek SB; 14.08.2011
comment
Кроме того, не путайте выделение/освобождение с построением/уничтожением. Первая — это работа оператора, вторая — работа выражения. Поскольку нет выражения для удаления размещения, вам нужно вызвать деструктор вручную. - person Kerrek SB; 14.08.2011

Нет, потому что новое размещение не выделяет никакой памяти. Вы используете новое размещение в ранее выделенной необработанной памяти. Единственное, что он делает, это вызывает конструктор объекта.

person Christian Rau    schedule 14.08.2011