Припустимо, у мене є такий код:
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*
до видалення?
Відповіді:
Це залежить від "безпечного". Зазвичай це спрацює, оскільки інформація зберігається разом із вказівником про сам розподіл, тож угодник може повернути його в потрібне місце. У цьому сенсі це "безпечно", поки ваш розподільник використовує внутрішні межі меж. (Багато хто робить.)
Однак, як було зазначено в інших відповідях, видалення недійсного вказівника не викликає деструкторів, що може бути проблемою. У цьому сенсі це не "безпечно".
Немає вагомих причин робити те, що ви робите так, як ви це робите. Якщо ви хочете написати власні функції розсилки, ви можете використовувати шаблони функцій для генерації функцій правильного типу. Важливою причиною для цього є створення розподільників пулів, які можуть бути надзвичайно ефективними для конкретних типів.
Як зазначалося в інших відповідях, це невизначена поведінка в C ++. Загалом добре уникати невизначеної поведінки, хоча сама тема складна і наповнена суперечливими думками.
sizeof(T*) == sizeof(U*)
для всіх T,U
свідчить про те, що має бути можливо мати 1 не шаблонну void *
реалізацію збирача сміття. Але тоді, коли gc насправді повинен видалити / звільнити покажчик, саме таке питання виникає. Для того, щоб це працювало, вам або потрібні обгортки деструкторних функцій лямбда (urgh), або вам знадобиться якась динамічна річ "тип як дані", яка дозволяє переходити між типом і тим, що зберігається.
Видалення за допомогою покажчика void не визначено стандартом C ++ - див. Розділ 5.3.5 / 3:
У першій альтернативі (об’єкт видалення), якщо статичний тип операнда відрізняється від його динамічного типу, статичний тип повинен бути базовим класом динамічного типу операнда, а статичний тип повинен мати віртуальний деструктор або поведінка не визначена . У другому варіанті (видалити масив), якщо динамічний тип об’єкта, що підлягає видаленню, відрізняється від його статичного типу, поведінка невизначена.
І його виноска:
Це означає, що об'єкт не можна видалити за допомогою вказівника типу void *, оскільки немає об'єктів типу void
.
NULL
якоюсь мірою для управління пам’яттю додатків?
Це не гарна ідея і не те, що ви робили б на C ++. Ви втрачаєте інформацію про тип без причини.
Ваш деструктор не буде викликаний для об'єктів у вашому масиві, які ви видаляєте, коли викликаєте його для не примітивних типів.
Натомість слід замінити нове / видалити.
Видалення void *, можливо, випадково звільнить пам'ять правильно, але це неправильно, оскільки результати невизначені.
Якщо з якихось невідомих мені причин вам потрібно зберегти вказівник у порожнечі *, то звільніть його, вам слід використовувати malloc і free.
Питання не має сенсу. Ваша плутанина може бути частково пов’язана з недбалою мовою, якою люди часто користуються delete
:
Ви використовуєте delete
для знищення об’єкта, який був динамічно розподілений. Зробивши це, ви формуєте вираз видалення з вказівником на цей об’єкт . Ви ніколи не "видаляєте вказівник". Ви насправді "видаляєте об'єкт, який ідентифікується за його адресою".
Тепер ми бачимо, чому питання не має сенсу: покажчик void не є "адресою об'єкта". Це просто адреса, без будь-якої семантики. Він може виходити з адреси реального об'єкта, але ця інформація втрачається, так як він був закодований в типі вихідного покажчика. Єдиний спосіб відновити покажчик об’єкта - повернути покажчик void назад до покажчика об’єкта (що вимагає від автора знання, що означає вказівник). void
сам по собі є неповним типом і, отже, ніколи не є типом об'єкта, а порожній вказівник ніколи не може використовуватися для ідентифікації об'єкта. (Об’єкти ідентифікуються спільно за типом та адресою.)
delete
може бути нульовим значенням покажчика, вказівником на об'єкт, що не є масивом, створеним попереднім новим виразом , або вказівником на суб'єкт, що представляє базовий клас такого об'єкта. Якщо ні, поведінка не визначена. " Тож якщо компілятор приймає ваш код без діагностики, це не що інше, як помилка в компіляторі ...
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);
}
Директора не покличуть.
Якщо ви хочете використовувати 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
безпечно користуватися (разом з , трохи додаткової роботи). Це робиться шляхом виділення порожнього вказівника в область пам’яті (яка називається «ареною»), а потім подання вказівника на арену в нову. Дивіться цей розділ у поширених запитаннях на C ++ . Це загальний підхід до впровадження пулів пам'яті в C ++.
Навряд чи є причина для цього.
Перш за все, якщо ви не знаєте тип даних, і все, що ви знаєте, це те, що ви void*
, то вам справді слід просто розглядати ці дані як нетипову крапку двійкових даних ( unsigned char*
) і використовувати malloc
/ free
для роботи з ними . Іноді це потрібно для таких речей, як дані форми сигналу тощо, де вам потрібно передати void*
вказівники на C apis. Добре.
Якщо ви робите знати тип даних (тобто має CTOR / dtor), але з якоїсь - то причини ви закінчили з void*
покажчиком (за якою - небудь причини у вас є) , то ви дійсно повинні кинути його назад до типу ви знаєте його бути , і закликати delete
це.
Я використовував void *, (він же невідомі типи) в моїй рамці, перебуваючи під час відображення коду та інших подвигів неоднозначності, і поки що у мене не було проблем (витік пам'яті, порушення доступу тощо) з будь-яких компіляторів. Лише попередження через те, що операція нестандартна.
Це абсолютно має сенс видалити невідоме (пустота *). Просто переконайтесь, що покажчик дотримується цих вказівок, інакше це може перестати мати сенс:
1) Невідомий вказівник не повинен вказувати на тип, який має тривіальний деконструктор, і тому, коли його кастирують як невідомий покажчик, його НІКОЛИ НЕ БУДУТЬ ВІДДАЛЕНО. Видаліть невідомий покажчик лише ПІСЛЯ повернення його назад до типу ОРИГІНАЛ.
2) Чи посилається на екземпляр як на невідомий вказівник у пам’яті, пов’язаній зі стеком чи купою? Якщо невідомий покажчик посилається на екземпляр у стеку, його НІКОЛИ НЕ ВИДАЛЯЙТЕ!
3) Ви на 100% впевнені, що невідомий покажчик є дійсним регіоном пам'яті? Ні, тоді це НІКОЛИ НЕ БУДЕ ВДАЛЕНО!
Загалом, дуже мало безпосередньої роботи, яка може бути виконана за допомогою невідомого (void *) типу покажчика. Однак опосередковано void * є великим активом для розробників C ++, коли потрібно розраховувати на неоднозначність даних.
Якщо ви просто хочете буфер, використовуйте malloc / free. Якщо вам потрібно використовувати новий / видалити, розгляньте тривіальний клас обгортки:
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) зазвичай один, тому аргументу вирівнювання також немає. У випадку рідкісних платформ, де розмір (char) не один, вони виділяють пам'ять, вирівняну для їх char. Тож аргумент вирівнювання також є спірним.
malloc / free у цьому випадку буде швидшим. Але ви втрачаєте std :: bad_alloc і повинні перевірити результат malloc. Виклик глобальних операторів new та delete може бути кращим, оскільки це обходить середнього.
new
насправді визначено кинути. Це не правда. Це компілятор і перемикач компілятора. Дивіться, наприклад, /GX[-] enable C++ EH (same as /EHsc)
комутатори MSVC2019 . Також на вбудованих системах багато хто вирішує не платити податки на продуктивність за винятки на C ++. Тож речення, що починається з "Але ви втрачаєте std :: bad_alloc ...", є сумнівним.