Чи безпечно видалити покажчик NULL?


299

Чи безпечно видалити покажчик NULL?

І це гарний стиль кодування?


21
Доброю практикою є написання програм C ++ без єдиного дзвінка на delete. Замість цього використовуйте RAII . Тобто, використовуйте std::vector<T> v(100);замість T* p = new T[100];, використовуйте розумні вказівники, як unique_ptr<T>і shared_ptr<T>які дбають про видалення замість сирих вказівників тощо.
fredoverflow

8
завдяки make_shared(C ++ 11) і make_unique(C ++ 14) програма повинна містити нуль в newіdelete
sp2danny

2
Ще можуть бути рідкісні випадки, які потребують нового / видалення, наприклад, атомний <T *>: atomic <unique_ptr <T>> заборонено, а атомний <shared_ptr <T>> має накладні витрати, які в деяких випадках можуть бути неприйнятними.
atb

2
Щоб оголосити клас з керуванням ресурсами за допомогою RAII, вам потрібно зателефонувати новий і видалити правильно ?, або ви говорите, що існує якийсь клас шаблонів, щоб приховати це навіть цим.
VinGarcia

2
@VinGarcia Справа в тому, що більшість користувачів / клієнтів (тобто небібліотечний ) код ніколи не повинні писати newабо delete. Класи, призначені для управління ресурсами, де стандартні компоненти не можуть виконати роботу, але, звичайно, можуть робити те, що їм потрібно, але справа в тому, що вони роблять некрасиві речі з керованою ними пам'яттю, а не кодом кінцевого користувача. Отже, зробіть власну бібліотеку / клас помічників для виконання new/ deleteта використовуйте цей клас замість них.
підкреслюй_d

Відповіді:


265

deleteтак чи інакше виконує перевірку, тому перевірка на вашій стороні додає накладні витрати і виглядає більш потворною. Дуже хороша практика встановлює покажчик на NULL після delete(допомагає уникнути подвійного видалення та інші подібні проблеми з пошкодженням пам'яті).

Я також хотів би, якщо deleteза замовчуванням параметр встановив значення NULL, як у

#define my_delete(x) {delete x; x = NULL;}

(Я знаю про значення R і L, але хіба це не було б добре?)


72
Зауважте, що все ще може бути кілька інших покажчиків, що вказують на один і той же об'єкт, навіть якщо ви встановили на NULL при видаленні.
sth

13
У більшості випадків у моєму коді вказівник виходить за межі, коли він буде видалений. Набагато безпечніше, ніж просто встановити його на NULL.
jalf

142
Дуже бог практика НЕ встановлюючи покажчик на NULL після видалення. Встановлення покажчика на NULL після його видалення маскує помилки розподілу пам'яті, що дуже погано. Правильна програма не видаляє вказівник двічі, а програма, яка видаляє вказівник двічі, повинна вийти з ладу.
Деймон

15
@Alice: Не має значення, що говорить стандарт у цьому відношенні. Стандарт, визначений для видалення нульового вказівника, дійсний з якихось абсурдних причин 30 років тому, тому він є законним (швидше за все, це спадщина C). Але видалення одного і того ж вказівника двічі (навіть після зміни його бітового шаблону) все ще є серйозною помилкою програми. Не за формулюванням стандарту, а за програмною логікою та власністю. Як і видалення нульового вказівника, оскільки нульовий покажчик не відповідає жодному об'єкту , тому нічого неможливо видалити. Програма повинна точно знати, чи об’єкт дійсний і хто ним володіє, і коли його можна видалити.
Деймон

27
@Damon Однак, незважаючи на ці відміни ваших драконівських правил власності, незамкнені структури, можливо, більш надійні, ніж такі, що базуються на замках. І так, мої колеги дійсно люблять мене за покращений профіль виконання, який ці структури забезпечують, і сувора безпека потоку, яку вони підтримують, що дозволяє простіше міркувати про код (чудово для обслуговування). Однак жодне з цього, а також натякнутий на вас особистий напад не мають жодного визначення правильності, дійсності чи права власності. Те, що ви пропонуєте, є хорошим правилом, але це не є універсальним законом і не закріплено в стандарті.
Аліса

77

Від проекту C ++ 0x проекту Стандарт.

$ 5.3.5 / 2 - "[...] У будь-якій альтернативі значення операнду видалення може бути нульовим значенням вказівника. [... '"

Звичайно, ніхто ніколи не буде "видаляти" вказівник зі значенням NULL, але це безпечно зробити. В ідеалі не повинно бути коду, який видаляє вказівник NULL. Але іноді корисно, коли видалення покажчиків (наприклад, у контейнері) відбувається в циклі. Оскільки видалити значення вказівника NULL безпечно, можна дійсно записати логіку видалення без явних перевірок на операнд NULL для видалення.

На додаток, C Standard $ 7.20.3.2 також говорить про те, що "безкоштовно" вказівник NULL не робить ніяких дій.

Вільна функція призводить до того, що простір, на який вказує ptr, буде розміщений, тобто надається для подальшого розподілу. Якщо ptr - нульовий покажчик, жодних дій не відбувається.


2
Мені б дуже хотілося, щоб ця відповідь була цитованою, якби вона навмисно не ввела неефективність в неоптимізований код. Як зазначено у прийнятій відповіді, видалення нульового вказівника - неоперативне. Тому перевірка того, чи є вказівник нульовим перед його видаленням, є абсолютно стороннім.
кодетаку

47

Так, це безпечно.

Видалення нульового вказівника немає ніякої шкоди; це часто скорочує кількість тестів в кінці функції, якщо неподілені вказівники ініціалізуються до нуля, а потім просто видаляються.


Оскільки попереднє речення викликало плутанину, приклад - який не є виключенням безпечним - того, що описується:

void somefunc(void)
{
    SomeType *pst = 0;
    AnotherType *pat = 0;

    
    pst = new SomeType;
    
    if (…)
    {
        pat = new AnotherType[10];
        
    }
    if (…)
    {
        code using pat sometimes
    }

    delete[] pat;
    delete pst;
}

Є всілякі гниди, які можна вибрати за допомогою зразкового коду, але концепція (я сподіваюся) зрозуміла. Змінні вказівника ініціалізуються до нуля, так що deleteоперації в кінці функції не потрібно перевіряти, чи є вони ненулевими у вихідному коді; код бібліотеки виконує цю перевірку в будь-якому випадку.


Мені довелося прочитати це кілька разів, щоб зрозуміти це. Ви повинні мати на увазі ініціалізацію їх до нуля у верхній частині методу, або під час нього, не в хвіст, безумовно? В іншому випадку ви просто видалите як нулювання, так і видалення.
Маркіз Лорн

1
@EJP: А , в повному обсязі неправдоподібна контур функції може бути: void func(void) { X *x1 = 0; Y *y1 = 0; … x1 = new[10] X; … y1 = new[10] Y; … delete[] y1; delete[] x1; }. Я не показав жодної структури блоку чи стрибків, але delete[]операції в кінці є безпечними через ініціалізації на початку. Якщо щось прискочило до кінця після того, як x1було виділено і раніше y1було виділено, і не було ініціалізації y1, то не було б визначеної поведінки - і, хоча код може перевірити недійсність ( x1і y1) перед видаленнями, робити це не потрібно. так.
Джонатан Леффлер

22

Видалення нульового покажчика не впливає. Це не гарний стиль кодування обов'язково, тому що він не потрібен, але і непоганий.

Якщо ви шукаєте належну практику кодування, замість цього використовуйте смарт-покажчики, тоді вам це зовсім не потрібно delete.


20
час, коли люди хочуть видалити покажчик NULL, це коли вони не впевнені, чи не містить він NULL ... якби вони знали, що це NULL, вони б не розглядали питання про видалення і тому запитували ;-).
Тоні Делрой

@Tony: Моя думка полягала лише в тому, що це не матиме ефекту, а наявність такого коду, який видаляє вказівник, який іноді містить NULL, не обов'язково є поганим.
Брайан Р. Бонді

3
Зайві перевірки IMO, безумовно, погані щодо продуктивності, читабельності та ремонтопридатності.
паульм

@paulm OP, безумовно, не говорить про такий поганий, більше про Seg Fault / UB різновид поганого.
Зима

8

Щоб доповнити відповідь русліка , на C ++ 14 ви можете використовувати цю конструкцію:

delete std::exchange(heapObject, nullptr);

3

Це безпечно, якщо ви не перевантажили оператора видалення. якщо ви перевантажили оператора видалення і не обробляєте нульовий стан, це зовсім не безпечно.


Чи можете ви додати якісь пояснення до своєї відповіді?
Marcin Nabiałek

-2

Я переконався, що видалити [] NULL (тобто синтаксис масиву) не безпечно (VS2010). Я не впевнений, чи відповідає це стандарту C ++.

Він є безпечним для видалення NULL (скалярна синтаксис).


6
Це незаконно, і я не вірю.
Конрад Рудольф

5
Ви повинні мати змогу видалити будь-який нульовий покажчик. Тож якщо для вас це зламається, то, мабуть, у вас є помилка в коді, який це показує.
Містичне

3
§5.3.2 У другому альтернативному варіанті (видалити масив) значення операнду видалення може бути нульовим значенням вказівника або значенням вказівника, яке було результатом попереднього масиву нового виразу.
sp2danny

1
@Opux Поведінка VS2010, зазначається у відповіді. Як пояснено в інших коментарях, це безпечно delete[] NULL.
Конрад Рудольф

1
@Opux Тому я написав "я не вірю в це", а не "це неправильно". Але я все одно ні, і це було б досить обурливим, дурним порушенням стандарту. VC ++ насправді досить добре дотримується обмежень стандарту, і місця, де він їх порушує, мають сенс історично.
Конрад Рудольф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.