Встановлення змінної на NULL після безкоштовного


156

У моїй компанії є правило кодування, яке говорить, звільнивши будь-яку пам'ять, скинути змінну до NULL. Наприклад ...

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

Я вважаю, що у таких випадках, як код, показаний вище, встановлення NULLзначення не має жодного значення. Або я щось пропускаю?

Якщо в таких випадках немає сенсу, я збираюся взяти це з командою з якості, щоб видалити це правило кодування. Будь ласка, порадь.


2
завжди корисно мати можливість перевірити, чи ptr == NULLперед цим щось робити. Якщо ви не скасуєте свої вільні покажчики, які ви отримаєте, ptr != NULLале все ще непридатні вказівники.
Ki Jéy

Данінг-покажчики можуть призвести до вразливих вразливостей, таких як Use-After-Free .
Константин Ван

Відповіді:


285

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

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

Для завершення стилю також слід ініціалізувати покажчики на NULL, перш ніж їм буде призначено справжнє значення вказівника.


3
Я не розумію, чому ви "ініціалізуєте покажчики на NULL, перш ніж їм буде призначено справжнє значення вказівника"?
Пол Біггар

26
@Paul: У конкретному випадку декларація може бути прочитана int *nPtr=NULL;. Тепер я погодився б, що це було б зайвим, з маликом, наступним праворуч у наступному рядку. Однак якщо між декларацією та першою ініціалізацією є код, хтось може почати використовувати змінну, хоча вона ще не має значення. Якщо нульова ініціалізація, ви отримуєте segfault; без цього ви можете знову читати чи записувати випадкову пам'ять. Крім того, якщо змінна пізніше стає ініціалізованою лише умовно, пізніші несправні звернення повинні дати вам миттєві збої, якщо ви пам’ятаєте про нульову ініціалізацію.
Мартін проти Левіса

1
Я особисто думаю, що в будь-якій нетривіальній кодовій базі отримання помилки для перенаправлення нуля настільки ж невиразно, як отримання помилки для перенаправлення адреси, якою ви не володієте. Мене особисто ніколи не турбує.
wilhelmtell

9
Вільгельме, справа в тому, що з відміною нульового вказівника ви отримуєте визначений збій і власне місце проблеми. Неправильний доступ може або не може зірватися, і несподівано зіпсувати дані чи поведінку у несподіваних місцях.
Аміт Найду

4
Власне, ініціалізація вказівника на NULL має принаймні один істотний недолік: це може завадити компілятору попереджати вас про неініціалізовані змінні. Якщо тільки логіка вашого коду насправді явно не обробляє це значення для вказівника (тобто якщо (nPtr == NULL) дозометується щось…), краще залишити його таким, яким є.
Ерік

37

Встановлення покажчика на NULLпісля free- це сумнівна практика, яка часто популяризується як правило «правильного програмування» на явно помилковій умові. Це одна з тих підроблених істин, які належать до категорії "звучить правильно", але насправді не досягають абсолютно нічого корисного (а іноді призводить до негативних наслідків).

Нібито встановлення вказівника на NULLafter freeповинно запобігти жахливій проблемі "подвійного вільного", коли одне і те ж значення вказівника передається freeбільше одного разу. Насправді, у 9 випадках із 10 справжня проблема "подвійного вільного" виникає, коли в якості аргументів використовуються різні об'єкти вказівників, що мають однакове значення вказівника free. Потрібно сказати, що встановлення покажчика NULLпісля не freeдосягає абсолютно нічого, щоб запобігти проблемі в таких випадках.

Звичайно, можна зіткнутися з проблемою "подвійного вільного" при використанні того ж об'єкта вказівника, що і аргумент free. Однак насправді подібні ситуації зазвичай вказують на проблему із загальною логічною структурою коду, а не просто випадковим "подвійним вільним". Правильним способом вирішення проблеми в таких випадках є перегляд та переосмислення структури коду, щоб уникнути ситуації, коли один і той же покажчик передається freeне один раз. У таких випадках встановлення покажчика NULLта розгляд проблеми «виправленою» - це не що інше, як спроба підмітати проблему під килим. У загальному випадку це просто не буде працювати, оскільки проблема зі структурою коду завжди знайде інший спосіб проявити себе.

Нарешті, якщо ваш код спеціально розроблений для того, щоб покластись на значення вказівника, яке є NULLчи ні NULL, цілком чудово встановити значення вказівника NULLпісля free. Але як загальне правило "належної практики" (як, наприклад, "завжди встановлюйте свій покажчик на NULLпісля free"), це, знову ж таки, відомий і досить марний фейк, за яким часто супроводжуються деякі з чисто релігійних, вуду-подібних причин.


1
Безумовно. Я не пам’ятаю, щоб коли-небудь викликати подвійну вільну, яку було б виправлено, встановивши покажчик на NULL після звільнення, але я спричинив багато цього.
LnxPrgr3

4
@AnT "сумнівний" - це небагато. Все залежить від випадку використання. Якщо значення вказівника коли-небудь використовується в істинному / хибному розумінні, це не тільки дійсна практика, це найкраща практика.
Coder

1
@Coder Повністю помиляється. Якщо значення вказівника використовується в істинному помилковому розумінні, щоб знати, чи вказувало воно на об’єкт перед закликом вивільнити, це не тільки не найкраща практика, це неправильно . Наприклад: foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;. Тут, встановивши barдля NULLпісля виклику freeвикликатиме функцію думати , що ніколи не було бару і повернути неправильне значення!
Девід Шварц

Я не думаю, що головна вигода - захист від подвійного вільного, скоріше - це ловити звисаючі покажчики раніше і надійніше. Наприклад, звільняючи структуру, яка містить ресурси, вказівники на виділену пам'ять, ручки файлів тощо, оскільки я звільняю вказівники пам'яті, що містяться, і закриваю файли, що містяться, я NULL відповідних членів. Тоді, якщо один з ресурсів отримати доступ через висячий покажчик помилково, програма, як правило, щоразу виходить з ладу. В іншому випадку без NULLing звільнені дані ще не можуть бути перезаписані, а помилка може бути легко відтворена.
jimhark

1
Я погоджуюся, що добре структурований код не повинен допускати випадку, коли вказівник звертається після звільнення, або випадку, коли він звільняється два рази. Але в реальному світі мій код буде модифікований та / або керований тим, хто, ймовірно, не знає мене і не має часу та / або навичок робити все належним чином (оскільки термін завжди вчора). Тому я схильний писати бронезахисні функції, які не руйнують систему навіть при неправильному використанні.
mfloris

34

Більшість відповідей були зосереджені на запобіганні подвійного вільного, але встановлення покажчика на NULL має ще одну перевагу. Після звільнення вказівника ця пам'ять буде доступна для перерозподілу за допомогою іншого дзвінка на malloc. Якщо у вас все ще є оригінальний вказівник навколо вас, можливо, з’явиться помилка, коли ви намагаєтеся використовувати вказівник після звільнення та пошкодження якоїсь іншої змінної, і тоді ваша програма переходить у невідомий стан, і всі види поганих речей можуть статися (аварія, якщо ви щастить, корупція даних, якщо вам не пощастило). Якщо ви встановили вказівник на NULL після вільної, будь-яка спроба читання / запису через цей покажчик пізніше призведе до сегментації, що, як правило, є кращим для випадкової пошкодження пам'яті.

З обох причин може бути хорошою ідеєю встановити вказівник на NULL після free (). Однак це не завжди потрібно. Наприклад, якщо змінна вказівника виходить із сфери застосування відразу після free (), немає великої причини встановити її на NULL.


1
+1 Це насправді дуже хороший момент. Не міркування про "подвійну безкоштовно" (що є абсолютно хибною), але це . Я не є прихильником механічних NULL-ing покажчиків після free, але це насправді має сенс.
ANT

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

1
@DavidSchwartz: Я не згоден з вашим коментарем. Коли мені довелося написати стік для університетських вправ кілька тижнів тому, у мене виникла проблема, я досліджував кілька годин. Я отримав доступ до вже вільної пам'яті в якийсь момент (у вільних було кілька рядків занадто рано). А іноді це призводить до дуже дивної поведінки. Якщо я встановив би покажчик на NULL після його звільнення, був би "простий" segfault, і я би заощадив пару годин роботи. Тож +1 за цю відповідь!
mozzbozz

2
@katze_sonne Навіть зупинений годинник є правильним двічі на день. Набагато ймовірніше, що встановлення покажчиків на NULL приховає помилки, запобігаючи помилковому доступу до вже звільнених об'єктів від segfaulting в коді, який перевіряє NULL, а потім мовчки не може перевірити об'єкт, який він повинен був перевірити. (Можливо, може бути корисним встановлення покажчиків на NULL після безкоштовного в конкретних складах налагодження або встановлення їх на значення, відмінне від NULL, яке гарантовано на рівні по умолчанню. Але те, що ця дурість допомогла вам одного разу, не є аргументом на користь .)
Девід Шварц

@DavidSchwartz Ну, це звучить розумно ... Дякую за ваш коментар, я буду вважати це в майбутньому! :) +1
mozzbozz

20

Це вважається хорошою практикою, щоб уникнути перезапису пам'яті. У наведеній вище функції вона непотрібна, але часто, коли вона виконується, вона може виявити помилки програми.

Спробуйте щось замість цього:

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** __p = (p); free(*(__p)); *(__p) = NULL; } while (0)
#endif

DEBUG_VERSION дозволяє профайлювати вільний код налагоджувального коду, але вони функціонально однакові.

Редагувати : Додано робити ..., як запропоновано нижче, дякую.


3
Версія макросу має тонку помилку, якщо ви використовуєте її після оператора if без дужок.
Марк Викуп

Що з 0 (пустотою) 0? Цей код виконайте: if (x) myfree (& x); ще do_foo (); стає, якщо (x) {вільний (* (& x)); * (& x) = null; } недійсна 0; ще do_foo (); Інше - помилка.
jmucchiello

Цей макрос є ідеальним місцем для оператора комами: free ( (p)), * (p) = null. Зрозуміло, наступна проблема полягає в тому, що вона оцінюється * (p) двічі. Він повинен бути {void * _pp = (p); вільний (* _ pp); * _pp = null; } Чи не забава передпроцесором.
jmucchiello

5
Макрос не повинен бути в оголених дужках, він повинен бути в do { } while(0)блоці, щоб if(x) myfree(x); else dostuff();він не ламався.
Кріс Лутц

3
Як сказав Лутц, макро тіло do {X} while (0)- це ІМО - найкращий спосіб зробити макро тіло, яке "відчуває себе та працює" як функцію. Більшість компіляторів все одно оптимізують цикл.
Майк Кларк

7

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

Якщо ви встановите вказівник на NULL, тоді, якщо ви отримаєте доступ до нього, програма завжди збоїть із segfault. Не більше ,, іноді це працює '', не більше ,, збоїв непередбачувано ''. Це простіше налагодити.


5
Програма не завжди виходить з ладу з segfault. Якщо спосіб звернення до вказівника означає, що до нього застосовано достатньо великий зсув перед перенаправленням, він може дійти до адресної пам'яті: ((MyHugeStruct *) 0) -> fieldNearTheEnd. І це ще до того, як ви матимете справу з обладнанням, яке взагалі не має стандартного доступу до 0. Однак, швидше за все, програма вийде з ладу з segfault.
Стів Джессоп

7

Якщо встановити вказівник на freeпам'ять d, це означає, що будь-яка спроба отримати доступ до цієї пам’яті через вказівник негайно зазнає краху, замість того, щоб викликати невизначене поведінку. Це набагато простіше визначити, де все пішло не так.

Я можу бачити ваш аргумент: оскільки nPtrвиходить із сфери застосування відразу після цього nPtr = NULL, здається, немає причин встановлювати це NULL. Однак у випадку з structчленом або десь іншим, де вказівник не відразу виходить за межі області, це має більше сенсу. Не відразу видно, чи буде цей покажчик знову використовуватися за кодом, який не повинен його використовувати.

Ймовірно, правило вказане, не розрізняючи ці два випадки, тому що набагато складніше автоматично застосувати правило, не кажучи вже про те, щоб розробники дотримувалися його. Не шкода встановлювати покажчики NULLпісля кожної безкоштовної, але це має потенціал вказувати на великі проблеми.


7

найпоширеніша помилка в с - це подвійна безкоштовно. В основному ти робиш щось подібне

free(foobar);
/* lot of code */
free(foobar);

і в кінцевому підсумку це дуже погано, ОС намагається звільнити деяку вже звільнену пам'ять і взагалі її за замовчуванням. Тож хороша практика полягає у встановленні NULL, щоб ви могли зробити тест і перевірити, чи дійсно вам потрібно звільнити цю пам'ять

if(foobar != null){
  free(foobar);
}

також слід зазначити, що free(NULL)нічого не буде робити, тому вам не доведеться писати заяву if. Я насправді не гуру ОС, але я досить навіть зараз, і більшість ОС зазнають аварій на подвійному безкоштовно.

Це також головна причина, чому всі мови зі збиранням сміття (Java, dotnet) так пишалися тим, що не мали цієї проблеми, а також не повинні залишати розробникам управління пам’яттю в цілому.


11
Ви можете насправді просто зателефонувати безкоштовно () без перевірки - безкоштовно (NULL) визначається як нічого не робити.
Бурштин

5
Це не приховує помилок? (Наче звільнення занадто багато.)
Георг Шоллі

1
ніж, я зрозумів. я спробував:p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
Шаобо Ван

5
Як я вже сказав, free(void *ptr) не можна змінити значення вказівника, яке воно передається. Він може змінювати вміст вказівника, дані, що зберігаються за цією адресою , але не саму адресу або значення вказівника . Для цього знадобиться free(void **ptr)(що, очевидно, не дозволено стандартом) або макрос (який дозволений і ідеально портативний, але люди не люблять макроси). Крім того, C не про зручність, це про те, щоб дати програмістам стільки контролю, скільки вони хочуть. Якщо вони не хочуть додавати накладні накладні показники встановлення NULL, вони не повинні їх примушувати.
Кріс Лутц

2
У світі є небагато речей, які б частково позбавили професіоналізму автора автори коду С. Але вони включають "перевірку покажчика на NULL перед викликом free" (поряд з такими речами, як "передавання результату функцій розподілу пам'яті" або "бездумне використання імен типів з sizeof").
ANT


4

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

Якщо ви NULL з покажчика NULL, то будь-яка спроба використовувати його буде перенаправлення 0x0 і аварія прямо там, що легко налагодити. Випадкові покажчики, що вказують на випадкову пам'ять, важко налагодити. Це, очевидно, не потрібно, але тоді це є в документі з найкращих практик.


Принаймні, в Windows, налагодження налагодження встановлять пам'ять на 0xdddddddd, тому коли ви використовуєте вказівник на видалену пам'ять, яку ви знаєте відразу. На всіх платформах мають бути подібні механізми.
i_am_jorf

2
jeffamaphone, видалений блок пам'яті, можливо, буде перерозподілений та призначений іншому об'єкту до моменту повторного використання покажчика.
Константин

4

Із стандарту ANSI C:

void free(void *ptr);

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

"невизначена поведінка" майже завжди є крахом програми. Щоб уникнути цього, безпечно скинути вказівник на NULL. Сам free () не може цього зробити, оскільки передається лише покажчик, а не вказівник на покажчик. Ви також можете написати більш безпечну версію free (), яка NULL вказує на вказівник:

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}

@DrPizza - Помилка (на мою думку) - це те, що змушує вашу програму не працювати так, як належить. Якщо прихований подвійний безкоштовний ламає вашу програму, це помилка. Якщо він працює саме так, як було призначено, це не помилка.
Кріс Лутц

@DrPizza: Я щойно знайшов аргумент, чому слід встановити його, NULLщоб уникнути маскування помилок. stackoverflow.com/questions/1025589/… Схоже, в будь-якому випадку деякі помилки приховуються.
Георг Шьоллі

1
Пам’ятайте, що недійсний покажчик на покажчик має свої проблеми: c-faq.com/ptrs/genericpp.html
Безпечний

3
@Chris, ні, найкращий підхід - це структура коду. Не кидайте випадкових лукавок та звільнянь у всій своїй кодовій базі, зберігайте пов’язані речі. "Модуль", який виділяє ресурс (пам'ять, файл, ...), відповідає за його звільнення і повинен забезпечити функцію для цього, що також несе турботу про покажчики. Для будь-якого конкретного ресурсу у вас є рівно одне місце, де він виділений, і одне місце, де він вивільняється, обидва близько.
Безпечний

4
@Chris Lutz: Hogwash. Якщо ви пишете код, який звільняє той самий вказівник двічі, програма має в ньому логічну помилку. Маскування цієї логічної помилки шляхом її збою не означає, що програма правильна: вона все ще робить щось безглуздо. Немає сценарію, в якому написання подвійної безкоштовної виправдано.
DrPizza

4

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

Однак - я не буду встановлювати змінну на null, якщо вказівник не повинен використовуватись знову, але часто дизайн вищого рівня дає мені підставу встановити його на нуль у будь-якому випадку. Наприклад, якщо вказівник є членом класу, і я видалив те, що це вказує, то "контракт", якщо вам сподобався клас, це те, що цей член вкаже на щось дійсне в будь-який час, тому його потрібно встановити на нуль з цієї причини. Невелика відмінність, але я вважаю важливою.

У c ++ важливо завжди думати, кому належать ці дані, коли ви виділяєте деяку пам’ять (якщо тільки ви не використовуєте розумні покажчики, але навіть тоді потрібна певна думка). І цей процес, як правило, призводить до того, що вказівники, як правило, є членами якогось класу, і, як правило, ви хочете, щоб клас знаходився у дійсному стані в усі часи, і найпростіший спосіб зробити це - встановити змінну члена на NULL, щоб вказати на це точки ні до чого.

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

Отже, підсумовуючи, так, я часто встановлюю вказівник на NULL після видалення чогось, але це як частина більшого дизайну і думок про те, кому належать дані, а не через сліпо слідування стандартному правилу кодування. Я б не робив цього у вашому прикладі, оскільки я думаю, що це не має ніякої користі, і це додає «захаращення», яке, на мій досвід, так само відповідає за помилки та поганий код, як і подібні речі.


4

Нещодавно я натрапив на те саме питання після того, як шукав відповідь. Я дійшов такого висновку:

Це найкраща практика, і потрібно дотримуватися цього, щоб зробити його портативним у всіх (вбудованих) системах.

free()- це функція бібліотеки, яка змінюється в міру зміни платформи, тому не слід сподіватися, що після передачі вказівника на цю функцію та після звільнення пам'яті цей покажчик буде встановлений на NULL. Це може бути не так у деяких бібліотеках, реалізованих для платформи.

тому завжди йдіть

free(ptr);
ptr = NULL;

3

Це правило корисно, коли ви намагаєтесь уникати таких сценаріїв:

1) У вас дійсно довга функція зі складним управлінням логікою та пам'яттю, і ви не хочете випадково повторно використовувати вказівник на видалену пам'ять пізніше у функції.

2) Покажчик - це змінна член класу, що має досить складну поведінку, і ви не хочете випадково повторно використовувати вказівник для видаленої пам'яті в інших функціях.

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

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

Загалом, я б радив вам встановити його на NULL, коли ви вважаєте, що це гарна ідея, і не турбуватися, коли ви думаєте, що цього не варто. Натомість зосередьтесь на написанні коротких функцій та добре розроблених класах.


2

Щоб додати до сказаного, один хороший метод використання вказівника - це завжди перевіряти, чи це дійсний покажчик чи ні. Щось на зразок:


if(ptr)
   ptr->CallSomeMethod();

Явне позначення покажчика як NULL після звільнення дозволяє використовувати такий тип використання в C / C ++.


5
У багатьох випадках, коли вказівник NULL не має сенсу, бажано замість цього написати твердження.
Еріх Кітцмюллер

2

Це може бути більше аргументом, щоб ініціалізувати всі покажчики на NULL, але щось подібне може бути дуже підлим помилкою:

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

pзакінчується там же, що і в стеку, як і попередній nPtr, тому він все ще може містити, здавалося б, дійсний вказівник. Призначення *pможе замінити всі види непов'язаних речей і призвести до некрасивих помилок. Особливо, якщо компілятор ініціалізує локальні змінні з нулем у режимі налагодження, але не одноразово вмикає оптимізацію. Таким чином, налагодження налагодження не показують жодних ознак помилки, а версії релізів підірваються випадковим чином ...


2

Встановити щойно звільнений покажчик на NULL не є обов'язковим, але є хорошою практикою. Таким чином, ви можете уникнути 1) використання звільненого загостреного 2) звільнення його на суші


2

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

Подвійне вільне призводить до невизначеної поведінки - зазвичай купує корупцію або негайно завершує програму. Виклик free () для покажчика NULL не робить нічого, тому гарантується, що він безпечний.

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


2

Ідея полягає в тому, що якщо ви спробуєте знеструмити вже не дійсний покажчик після його звільнення, ви хочете вийти з ладу (segfault), а не мовчки та загадково.

Але ... будьте обережні. Не всі системи викликають значення сегмента за замовчуванням, якщо ви відпустите NULL. Увімкнено (принаймні деякі версії) AIX, * (int *) 0 == 0, і Solaris має додаткову сумісність з цією "функцією" AIX.


2

До початкового питання: Встановлення вказівника на NULL безпосередньо після звільнення вмісту є повною витратою часу, за умови, що код відповідає всім вимогам, повністю налагоджено і більше ніколи не буде змінено. З іншого боку, звільнений NULLing звільнений вказівник може бути дуже корисним, коли хтось бездумно додає новий блок коду під free (), коли конструкція оригінального модуля не є правильною, і у випадку з ним -компілює-але-не-робить-чого-я хочу помилки.

У будь-якій системі є незрозуміла мета - зробити її найпростішою правильною справою, і незнижуюча вартість неточних вимірювань. В C нам пропонують набір дуже гострих, дуже міцних інструментів, які можуть створити багато речей в руках кваліфікованого працівника та завдати всілякі метафоричні травми при неправильному поводженні. Деяких важко зрозуміти або використовувати правильно. А люди, будучи природно проти ризику, роблять ірраціональні речі, такі як перевірка покажчика на значення NULL, перш ніж зателефонувати за допомогою нього…

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

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


Хороша відповідь. Я хотів би додати одне. Перегляд бази помилок добре робити з різних причин. Але в контексті оригінального запитання майте на увазі, що важко буде дізнатися, скільки недійсних проблем із вказівниками було попереджено або принаймні зафіксовано настільки рано, що не потрапило в базу даних про помилки. Історія помилок забезпечує кращі докази для додавання правил кодування.
jimhark

2

Є дві причини:

Уникайте збоїв при подвійному звільненні

Написав RageZ у двох примірниках .

Найпоширеніша помилка в c - це подвійна безкоштовно. В основному ти робиш щось подібне

free(foobar);
/* lot of code */
free(foobar);

і в кінцевому підсумку це дуже погано, ОС намагається звільнити деяку вже звільнену пам'ять і взагалі її за замовчуванням. Тож хороша практика полягає у встановленні NULL, щоб ви могли зробити тест і перевірити, чи дійсно вам потрібно звільнити цю пам'ять

if(foobar != NULL){
  free(foobar);
}

також слід зазначити, що free(NULL) нічого не зробить, тому вам не доведеться писати заяву if. Я насправді не гуру ОС, але я досить навіть зараз, і більшість ОС зазнають аварій на подвійному безкоштовно.

Це також головна причина, чому всі мови зі збиранням сміття (Java, dotnet) так пишалися тим, що не мали цієї проблеми, а також не мали залишати розробника управління пам’яттю в цілому.

Уникайте використання вже звільнених покажчиків

Написав Мартін проти Левіса в іншій відповіді .

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

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

Для завершення стилю також слід ініціалізувати покажчики на NULL, перш ніж їм буде призначено справжнє значення вказівника.


1

Оскільки у вас є команда із забезпечення якості, дозвольте додати ще невелику точку щодо якості. Деякі автоматизовані засоби контролю якості для C позначать призначення звільненим покажчикам як "марне призначення ptr". Наприклад, PC-lint / FlexeLint від компанії Gimpel Software tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

Існують способи вибіркового придушення повідомлень, тому ви все ще можете задовольнити обидва вимоги щодо якості, якщо ваша команда вирішить.


1

Завжди доцільно оголосити змінну вказівника NULL, наприклад,

int *ptr = NULL;

Скажімо, ptr вказує на адресу пам'яті 0x1000 . Після використання free(ptr)завжди бажано звести нанівець змінну вказівника, оголосивши ще раз NULL . наприклад:

free(ptr);
ptr = NULL;

Якщо повторно не оголошено NULL , змінна вказівника все ще продовжує вказувати на ту саму адресу ( 0x1000 ), ця змінна вказівник називається звисаючим покажчиком . Якщо ви визначите іншу змінну вказівника (скажімо, q ) і динамічно розподіліть адресу до нового вказівника, є ймовірність прийняти ту саму адресу ( 0x1000 ) новою змінною вказівника. Якщо у випадку ви використовуєте той самий покажчик ( ptr ) і оновлюєте значення за адресою, вказаною тим самим вказівником ( ptr ), програма в кінцевому підсумку запише значення в місце, де вказується q (оскільки p і q є вказуючи на ту саму адресу (0х1000 )).

напр

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.

1

Короткий короткий огляд: Ви не хочете випадково (помилково) отримати доступ до визволеної адреси. Тому що, звільняючи адресу, ви дозволяєте вказати цю адресу в купі для якоїсь іншої програми.

Однак якщо ви не встановите вказівник на NULL і помилково спробуйте де-посилати покажчик або змінити значення цієї адреси; ВИ МОЖЕТЕ ВЗАЄМО ВИРОБИТИ. АЛЕ НЕ ЧОГО, ЩО ВИ БОГО ЛОГІЧНО ХОЧУТЬ ЗАВЕРШИТИ.

Чому я все-таки можу отримати доступ до звільненого від мене місця пам'яті? Тому що: у вас може бути вільна пам'ять, але змінна вказівника все ж мала інформацію про адресу пам'яті купи. Отож, як оборонну стратегію, будь ласка, встановіть її на NULL.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.