Предположим, у меня есть следующий код:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
Это безопасно? Или ptr нужно преобразовать в char* перед удалением?
Предположим, у меня есть следующий код:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
Это безопасно? Или ptr нужно преобразовать в char* перед удалением?
Это зависит от "безопасности". Обычно это работает, потому что информация о самом выделении хранится вместе с указателем, поэтому делокатор может вернуть ее в нужное место. В этом смысле это «безопасно», пока ваш распределитель использует внутренние граничные теги. (Многие делают.)
Однако, как упоминалось в других ответах, удаление указателя void не вызовет деструкторы, что может быть проблемой. В этом смысле он не «безопасен».
Нет веских причин делать то, что вы делаете, так, как вы это делаете. Если вы хотите написать свои собственные функции освобождения, вы можете использовать шаблоны функций для создания функций с правильным типом. Хорошей причиной для этого является создание распределителей пула, которые могут быть чрезвычайно эффективными для определенных типов.
Как упоминалось в других ответах, это неопределенное поведение в C++. В целом хорошо избегать неопределенного поведения, хотя сама тема сложна и наполнена противоречивыми мнениями.
sizeof(T*) == sizeof(U*) для всех T,U предполагает, что должна быть возможность иметь 1 не шаблонную, основанную на void * реализацию сборщика мусора. Но тогда, когда gc действительно должен удалить/освободить указатель, возникает именно этот вопрос. Чтобы заставить его работать, вам либо нужны оболочки деструктора лямбда-функции (ургх), либо вам понадобится какой-то динамический тип в качестве типа данных, который позволяет перемещаться между типом и чем-то сохраняемым.
- person BitTickler; 24.06.2019
unordered_map<void*,Something>), я нашел - вполне работоспособный, хотя и не очень эффективный способ сделать это: template <class T> struct Foo { void deleter(void*p) { delete reinterpret_cast<T*>(p); } }; Затем я сохранил указатель на эту функцию удаления в Something, в переход от типизированного к нетипизированному. Помимо массивов T, это должно быть прочным и легко расширяемым.
- person BitTickler; 25.06.2019
Удаление с помощью указателя void не определено стандартом C++ - см. раздел 5.3.5/3:
В первом варианте (удалить объект), если статический тип операнда отличается от его динамического типа, статический тип должен быть базовым классом динамического типа операнда, а статический тип должен иметь виртуальный деструктор или поведение не определено. . Во втором варианте (удаление массива), если динамический тип удаляемого объекта отличается от его статического типа, поведение не определено.
И его сноска:
Это означает, что объект не может быть удален с помощью указателя типа void*, потому что нет объектов типа void*.
.
NULL имеет ли какое-либо значение для управления памятью приложения?
- person artu-hnrq; 27.02.2020
Это не очень хорошая идея и не то, что вы бы сделали на С++. Вы теряете информацию о типе без причины.
Ваш деструктор не будет вызываться для объектов в вашем массиве, которые вы удаляете, когда вы вызываете его для не примитивных типов.
Вместо этого следует переопределить новое/удаление.
Удаление void*, вероятно, случайно правильно освободит вашу память, но это неправильно, потому что результаты не определены.
Если по какой-то неизвестной мне причине вам нужно сохранить свой указатель в пустоте *, а затем освободить его, вы должны использовать malloc и free.
Удаление пустого указателя опасно, потому что деструкторы не будут вызываться для значения, на которое он фактически указывает. Это может привести к утечке памяти/ресурсов в вашем приложении.
Вопрос не имеет смысла. Ваше замешательство может быть отчасти связано с небрежностью, которую люди часто используют с delete:
Вы используете delete для уничтожения объекта, который был размещен динамически. При этом вы формируете выражение удаления с указателем на этот объект. Вы никогда не «удаляете указатель». Что вы действительно делаете, так это «удаляете объект, который идентифицируется по его адресу».
Теперь мы понимаем, почему вопрос не имеет смысла: указатель void не является «адресом объекта». Это просто адрес, без какой-либо семантики. Он может исходить из адреса фактического объекта, но эта информация будет потеряна, поскольку она была закодирована в типе исходного указателя. Единственный способ восстановить указатель объекта — привести указатель void обратно к указателю объекта (что требует от автора знания значения указателя). void сам по себе является неполным типом и, следовательно, никогда не является типом объекта, а указатель void никогда не может использоваться для идентификации объекта. (Объекты идентифицируются совместно по их типу и их адресу.)
delete может быть нулевым значением указателя, указателем на объект, не являющийся массивом, созданный предыдущим new-expression или указатель на подобъект, представляющий базовый класс такого объекта. Если нет, поведение не определено. Итак, если компилятор принимает ваш код без диагностики, это не что иное, как ошибка в компиляторе...
- person Kerrek SB; 01.10.2013
delete void_pointer. Это неопределенное поведение. Программисты никогда не должны вызывать неопределенное поведение, даже если ответ делает то, что хотел программист.
- person David Hammen; 27.02.2014
NULL имеет какое-либо значение для управления памятью приложения?
- person artu-hnrq; 27.02.2020
Если вам действительно необходимо это сделать, почему бы не убрать посредника (операторы 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, если он зарегистрирован).
Потому что у char нет специальной логики деструктора. ЭТО не сработает.
class foo
{
~foo() { printf("huzza"); }
}
main()
{
foo * myFoo = new foo();
delete ((void*)foo);
}
Доктора не вызовут.
Если вы хотите использовать 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 (с , гм, немного дополнительной работы). Это делается путем размещения указателя void в области памяти (называемой «ареной»), а затем предоставления указателя на арену в new. См. этот раздел в часто задаваемых вопросах по C++. Это распространенный подход к реализации пулов памяти в C++.
Вряд ли для этого есть причины.
Прежде всего, если вы не знаете тип данных, и все, что вы знаете, это то, что это void*, то вам действительно следует рассматривать эти данные как бестиповый большой двоичный объект strong> двоичных данных (unsigned char*) и используйте malloc/free для работы с ним. Иногда это требуется для таких вещей, как данные формы сигнала и т.п., когда вам нужно передать void* указателей на C apis. Это нормально.
Если вы действительно знаете тип данных (т.е. у них есть ctor/dtor), но по какой-то причине вы получили указатель void* (по любой причине), то вы действительно следует вернуть его к известному вам типу и вызвать для него delete.
Я использовал void* (также известные как неизвестные типы) в своей структуре для отражения кода и других подвигов двусмысленности, и до сих пор у меня не было проблем (утечка памяти, нарушения прав доступа и т. д.) от любых компиляторов. Только предупреждения из-за нестандартной операции.
Вполне имеет смысл удалить неизвестное (void*). Просто убедитесь, что указатель следует этим рекомендациям, иначе он может потерять смысл:
1) Неизвестный указатель не должен указывать на тип с тривиальным деконструктором, поэтому при приведении к неизвестному указателю он НИКОГДА НЕ ДОЛЖЕН БЫТЬ УДАЛЕНЫМ. Удаляйте неизвестный указатель только ПОСЛЕ приведения его обратно к ОРИГИНАЛЬНОМУ типу.
2) На экземпляр ссылаются как на неизвестный указатель в памяти, привязанной к стеку или куче? Если неизвестный указатель ссылается на экземпляр в стеке, то он НИКОГДА НЕ ДОЛЖЕН УДАЛЯТЬСЯ!
3) Вы на 100% уверены, что неизвестный указатель является допустимой областью памяти? Нет, тогда это НИКОГДА НЕ ДОЛЖНО БЫТЬ УДАЛЕНО!
В целом, существует очень мало прямой работы, которую можно выполнить, используя неизвестный (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 может быть лучше, поскольку он обходит посредника.
new на самом деле определяется как бросок. Это неправда. Это зависит от компилятора и переключателя компилятора. См., например, переключатели MSVC2019 /GX[-] enable C++ EH (same as /EHsc). Кроме того, во встроенных системах многие предпочитают не платить налог на производительность за исключения C++. Таким образом, предложение, начинающееся с «Но вы теряете std::bad_alloc...», вызывает сомнения.
- person BitTickler; 24.06.2019
mallocиfreeнапрямую. Это неявно несет в себе тот факт, что конструктор/деструктор не будет вызываться (как говорят ответы ниже) для того, что лежит в основе этого указателяvoid*. - person bobobobo   schedule 07.05.2013delete (MyPluginBase*) aPointerи убедиться, чтоMyPluginимеет виртуальный деструктор. - person Derf Skren   schedule 04.07.2016