Безопасно ли удалять пустой указатель? - PullRequest
86 голосов
/ 03 июня 2009

Предположим, у меня есть следующий код:

void* my_alloc (size_t size)
{
   return new char [size];
}

void my_free (void* ptr)
{
   delete [] ptr;
}

Это безопасно? Или ptr должен быть приведен к char* до удаления?

Ответы [ 13 ]

136 голосов
/ 03 июня 2009

Удаление через указатель void не определено стандартом C ++ - см. Раздел 5.3.5 / 3:

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

И его сноска:

Это означает, что объект не может быть удалено с помощью указателя типа void * потому что нет объектов типа пустота

.

24 голосов
/ 03 июня 2009

Это не очень хорошая идея и не то, что вы бы делали в C ++. Вы теряете информацию о вашем типе без причины.

Ваш деструктор не будет вызываться для объектов в вашем массиве, которые вы удаляете, когда вызываете его для не примитивных типов.

Вместо этого вы должны переопределить новый / удалить.

Удаление пустоты *, вероятно, освободит вашу память случайно, но это неправильно, потому что результаты не определены.

Если по какой-то неизвестной мне причине вам необходимо сохранить указатель в пустоте *, то освободите его, используйте malloc и free.

20 голосов
/ 03 июня 2009

Зависит от "сейфа". Обычно это работает, потому что информация хранится вместе с указателем о самом выделении, поэтому освобождающий модуль может вернуть ее в нужное место. В этом смысле это «безопасно», пока ваш распределитель использует внутренние граничные теги. (Многие делают.)

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

Нет веской причины делать то, что вы делаете, так, как вы это делаете. Если вы хотите написать свои собственные функции освобождения, вы можете использовать шаблоны функций для генерации функций с правильным типом. Хорошая причина сделать это - создать распределители пулов, которые могут быть чрезвычайно эффективными для определенных типов.

Как уже упоминалось в других ответах, это неопределенное поведение в C ++. В общем, хорошо избегать неопределенного поведения, хотя сама тема сложна и наполнена противоречивыми мнениями.

13 голосов
/ 03 июня 2009

Удаление пустого указателя опасно, поскольку деструкторы не будут вызываться для значения, на которое он фактически указывает. Это может привести к утечке памяти / ресурсов в вашем приложении.

7 голосов
/ 08 сентября 2013

Вопрос не имеет смысла. Ваша путаница может быть частично из-за небрежного языка, который люди часто используют с delete:

Вы используете delete для уничтожения объекта , который был динамически выделен. Для этого вы создаете выражение для удаления с указателем на этот объект . Вы никогда не «удаляете указатель». Что вы действительно делаете, это «удаляете объект, который идентифицируется по его адресу».

Теперь мы понимаем, почему вопрос не имеет смысла: указатель void не является «адресом объекта». Это просто адрес, без какой-либо семантики. Он может пришел с адреса реального объекта, но эта информация потеряна, потому что она была закодирована в тип исходного указателя. Единственный способ восстановить указатель на объект - вернуть указатель void обратно на указатель на объект (что требует от автора знать, что означает указатель). void сам по себе является неполным типом и, следовательно, никогда не является типом объекта, и указатель void никогда не может использоваться для идентификации объекта. (Объекты определяются совместно по их типу и адресу).

5 голосов
/ 04 июня 2009

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

void* my_alloc (size_t size)
{
   return ::operator new(size);
}

void my_free (void* ptr)
{
   ::operator delete(ptr);
}

Обратите внимание, что в отличие от malloc(), operator new выдает std::bad_alloc при сбое (или вызывает new_handler, если он зарегистрирован).

5 голосов
/ 03 июня 2009

Если вы хотите использовать void *, почему бы вам не использовать только malloc / free? new / delete - это больше, чем просто управление памятью. По сути, new / delete вызывает конструктор / деструктор, и происходит больше вещей. Если вы просто используете встроенные типы (например, char *) и удаляете их через void *, это будет работать, но все же это не рекомендуется. Суть в том, чтобы использовать malloc / free, если вы хотите использовать void *. В противном случае вы можете использовать шаблонные функции для вашего удобства.

template<typename T>
T* my_alloc (size_t size)
{
   return new T [size];
}

template<typename T>
void my_free (T* ptr)
{
   delete [] ptr;
}

int main(void)
{
    char* pChar = my_alloc<char>(10);
    my_free(pChar);
}
5 голосов
/ 03 июня 2009

Многие люди уже прокомментировали, сказав, что нет, удалить указатель void небезопасно. Я согласен с этим, но я также хотел добавить, что если вы работаете с пустыми указателями для выделения смежных массивов или чего-то подобного, вы можете сделать это с new, чтобы вы могли использовать delete безопасно (с, гм, немного дополнительной работы). Это делается путем выделения пустого указателя на область памяти (называемой «ареной») и затем подачи указателя на арену для нового. См. Этот раздел в C ++ FAQ . Это общий подход к реализации пулов памяти в C ++.

5 голосов
/ 03 июня 2009

Потому что у char нет специальной деструкторной логики. Это не сработает.

class foo
{
   ~foo() { printf("huzza"); }
}

main()
{
   foo * myFoo = new foo();
   delete ((void*)foo);
}

Доктору не позвонят.

0 голосов
/ 01 февраля 2015

Для особого случая char.

char - это внутренний тип, который не имеет специального деструктора. Таким образом, аргументы утечки спорны.

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

malloc / free будет быстрее в этом случае. Но вы теряете std :: bad_alloc и должны проверить результат malloc. Вызов глобальных операторов new и delete может быть лучше, поскольку он обходит посредника.

...