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

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

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

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

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

13 ответов

Решение

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

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

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

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

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

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

И его сноска:

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

,

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

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

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

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

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

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

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

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

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

Если вы действительно должны это сделать, почему бы не вырезать среднего человека (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 небезопасно. Я согласен с этим, но я также хотел добавить, что если вы работаете с пустыми указателями для выделения смежных массивов или чего-то подобного, то вы можете сделать это с new так что вы сможете использовать delete безопасно (с, гм, немного дополнительной работы). Это делается путем выделения пустого указателя на область памяти (называемой "ареной") и затем подачи указателя на арену для нового. Смотрите этот раздел в C++ FAQ. Это общий подход к реализации пулов памяти в C++.

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

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

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

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

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

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

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

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

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

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

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

1) Неизвестный указатель не должен указывать на тип, имеющий тривиальный деконструктор, и поэтому при приведении в качестве неизвестного указателя он НИКОГДА НЕ БУДЕТ УДАЛЕН. Удалите только неизвестный указатель ПОСЛЕ приведения его обратно в ОРИГИНАЛЬНЫЙ тип.

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

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

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

Другие вопросы по тегам