Предположим, у меня есть следующий код:
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 не вызовет деструкторы, что может быть проблемой. В этом смысле это небезопасно.
Нет веской причины делать то, что вы делаете, так, как вы это делаете. Если вы хотите написать свои собственные функции освобождения, вы можете использовать шаблоны функций для создания функций с правильным типом. Хорошая причина для этого - создать распределители пула, которые могут быть чрезвычайно эффективными для определенных типов.
Как упоминалось в других ответах, это неопределенное поведение в С ++. В общем, лучше избегать неопределенного поведения, хотя сама тема сложна и наполнена противоречивыми мнениями.
sizeof(T*) == sizeof(U*)
для всех T,U
предполагает, что должна быть возможность иметь void *
одну реализацию сборщика мусора , не основанную на шаблонах . Но тогда, когда gc действительно должен удалить / освободить указатель, возникает именно этот вопрос. Чтобы заставить его работать, вам либо понадобятся обертки деструктора лямбда-функции (urgh), либо вам понадобится что-то вроде динамического типа «тип как данные», который позволяет перемещаться между типом и чем-то хранимым.
Удаление с помощью указателя void не определено стандартом C ++ - см. Раздел 5.3.5 / 3:
В первой альтернативе (удалить объект), если статический тип операнда отличается от его динамического типа, статический тип должен быть базовым классом динамического типа операнда, а статический тип должен иметь виртуальный деструктор, или поведение не определено. . Во втором варианте (удалить массив), если динамический тип удаляемого объекта отличается от его статического типа, поведение не определено.
И его сноска:
Это означает, что объект нельзя удалить с помощью указателя типа void *, поскольку нет объектов типа void.
.
NULL
каким-либо образом изменить управление памятью приложения?
Это плохая идея и не то, что вы бы сделали на C ++. Вы без причины теряете информацию о своем типе.
Ваш деструктор не будет вызываться для объектов в вашем массиве, которые вы удаляете, когда вы вызываете его для не примитивных типов.
Вместо этого вы должны переопределить новый / удалить.
Удаление void *, вероятно, случайно освободит вашу память правильно, но это неправильно, потому что результаты не определены.
Если по какой-то неизвестной мне причине вам нужно сохранить указатель в void *, то освободите его, вы должны использовать malloc и free.
Вопрос не имеет смысла. Отчасти ваше замешательство может быть связано с небрежным языком, который люди часто используют с delete
:
Вы используете delete
для уничтожения объекта, который был размещен динамически. Сделайте это, вы создадите выражение удаления с указателем на этот объект . Вы никогда не «удаляете указатель». На самом деле вы «удаляете объект, который идентифицируется по его адресу».
Теперь мы видим, почему вопрос не имеет смысла: указатель void - это не «адрес объекта». Это просто адрес без какой-либо семантики. Он мог быть получен из адреса реального объекта, но эта информация потеряна, поскольку была закодирована в типе исходного указателя. Единственный способ восстановить указатель на объект - вернуть указатель void обратно к указателю на объект (что требует, чтобы автор знал, что означает указатель). void
сам по себе является неполным типом и, следовательно, никогда не является типом объекта, а указатель void никогда не может использоваться для идентификации объекта. (Объекты идентифицируются вместе по их типу и адресу.)
delete
может быть значением нулевого указателя, указателем на объект, не являющийся массивом, созданный предыдущим выражением new , или указателем на подобъект, представляющий базовый класс такого объекта. В противном случае поведение не определено. " Так что, если компилятор принимает ваш код без диагностики, это не что иное, как ошибка в компиляторе ...
delete void_pointer
. Это неопределенное поведение. Программисты никогда не должны вызывать неопределенное поведение, даже если кажется, что ответ делает то, что хотел программист.
Если вы действительно должны сделать это, почему бы не вырезать средний человек ( 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);
}
В 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);
}
Многие люди уже прокомментировали, что нет, удалять указатель void небезопасно. Я согласен с этим, но я также хотел добавить, что если вы работаете с указателями void для выделения смежных массивов или чего-то подобного, вы можете сделать это, new
чтобы вы могли delete
безопасно использовать (с, кхм , немного лишних работ). Это делается путем выделения пустого указателя на область памяти (называемую «ареной»), а затем передачи указателя на арену в new. См. Этот раздел в FAQ по C ++ . Это общий подход к реализации пулов памяти в C ++.
Вряд ли есть причина для этого.
Прежде всего, если вы не знаете тип данных, и все, что вы знаете, это void*
то, что вам действительно следует рассматривать эти данные как бесполезный двоичный объект данных ( 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
самом деле определено бросить. Это неправда. Это зависит от компилятора и переключателя компилятора. См., Например, /GX[-] enable C++ EH (same as /EHsc)
переключатели MSVC2019 . Также во встроенных системах многие предпочитают не платить налог на производительность за исключения C ++. Поэтому предложение, начинающееся с «Но вы лишаетесь std :: bad_alloc ...», вызывает сомнения.