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


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

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

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

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

13 84

13 ответов:

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

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

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

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

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

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

и сноска:

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

.

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

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

вместо этого вы должны переопределить new / delete.

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

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

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

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

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

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

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

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

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

d'ctor не будет вызван.

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

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

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

вряд ли есть причина для этого.

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

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

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

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

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

2) ссылается ли экземпляр как неизвестный указатель в памяти с привязкой к стеку или к куче? Если неизвестный указатель ссылается на экземпляр в стеке, то он никогда не должен быть удален!

3) Вы на 100% уверены, что неизвестный указатель является допустимой областью памяти? Нет, тогда он никогда не должен БЫТЬ DELTED!

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

Если вам просто нужен буфер, используйте 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;

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

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

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

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