Безопасно ли удалять указатель на пустоту?

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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

Удаление с помощью указателя void является undefined стандартом С++ - см. раздел 5.3.5/3:

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

И его сноска:

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

.

Ответ 3

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

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

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

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

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

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

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

Дорт не будет вызван.

Ответ 7

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

Ответ 8

Если вы хотите использовать 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);
}

Ответ 9

Если вы действительно должны это сделать, почему бы не вырезать среднего человека (операторы 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, если он зарегистрирован).

Ответ 10

Если вы просто хотите использовать буфер, используйте malloc/free. Если вы должны использовать new/delete, рассмотрите тривиальный класс-оболочку:

template<int size_ > struct size_buffer { 
  char data_[ size_]; 
  operator void*() { return (void*)&data_; }
};

typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer

OpaqueBuffer* ptr = new OpaqueBuffer();

delete ptr;

Ответ 11

Вряд ли причина для этого.

Прежде всего, если вы не знаете тип данных, и все, что вы знаете, это то, что оно void*, тогда вы действительно должны просто обрабатывать эти данные как беспринципные blob двоичных данных (unsigned char*) и используйте malloc/free для решения этой проблемы. Это требуется иногда для таких вещей, как данные формы волны и т.п., Где вам нужно передать указатели void* на C apis. Это прекрасно.

Если вы do знаете тип данных (т.е. он имеет ctor/dtor), но по какой-то причине вы получили указатель void* (по какой бы то ни было причине) , тогда вы действительно должны вернуть его к типу, который, как вы знаете,, и называть delete на нем.

Ответ 12

Я использовал void * (или неизвестные типы) в своей структуре, хотя в отражении кода и других подвигах двусмысленности, и до сих пор у меня не было проблем (утечка памяти, нарушения доступа и т.д.) от любых компиляторов, Только предупреждения из-за нестандартной работы.

Совершенно имеет смысл удалить неизвестное (void *). Просто убедитесь, что указатель соответствует этим рекомендациям или может перестать иметь смысл:

1) Неизвестный указатель не должен указывать на тип, который имеет тривиальный деконструктор, и поэтому, когда он отображается как неизвестный указатель, он НИКОГДА НЕ ДОЛЖЕН УДАЛИТЬ. Удалите неизвестный указатель AFTER, вернув его обратно в тип ORIGINAL.

2) Указывается ли экземпляр как неизвестный указатель на стеке или связанную с кучей память? Если неизвестный указатель ссылается на экземпляр в стеке, он НИКОГДА НЕ ДОЛЖЕН УДАЛИТЬСЯ!

3) Вы на 100% уверены, что неизвестный указатель является допустимым регистром памяти? Нет, тогда он НИКОГДА НЕ ДОЛЖЕН БЫТЬ УДАЛЕНЫ!

В целом, очень мало непосредственной работы, которая может быть выполнена с использованием неизвестного типа (void *). Однако, косвенно, void * является отличным преимуществом для разработчиков С++, чтобы полагаться, когда требуется двузначность данных.

Ответ 13

Для частного случая char.

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

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

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