Що означає робити "нульову перевірку" на C або C ++?


21

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

  • Що саме є нульовим?
  • Що означає "перевірити на нуль"?
  • Чи потрібно завжди перевіряти наявність нуля?

Будь-які приклади коду були б дуже вдячні.



Я б радив отримати кілька кращих навчальних посібників, якщо всі, хто ви читаєте, говорять про нульові перевірки, не пояснюючи їх ніколи та надаючи приклад код ...
underscore_d

Відповіді:


26

У C і C ++ вказівники за своєю суттю небезпечні, тобто, коли ви відтягуєте вказівник, ви зобов'язані переконатися, що він вказує десь дійсним; це частина того, про що йдеться в "ручному керуванні пам'яттю" (на відміну від схем автоматичного управління пам'яттю, реалізованих на таких мовах, як Java, PHP або .NET виконання, що не дозволить вам створювати недійсні посилання без значних зусиль).

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

void fill_foo(int* foo) {
    *foo = 23; // this will crash and burn if foo is NULL
}

Краща версія:

void fill_foo(int* foo) {
    if (!foo) { // this is the NULL check
        printf("This is wrong\n");
        return;
    }
    *foo = 23;
}

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


3
@MrLister Що ви маєте на увазі, нульові перевірки не працюють у C ++? Вам просто потрібно ініціалізувати покажчик на нуль, коли ви його оголошуєте.
TZHX

1
Що я маю на увазі, ви повинні пам'ятати, щоб встановити вказівник на NULL, інакше він не працюватиме. І якщо ви пам’ятаєте, іншими словами, якщо ви знаєте, що вказівник NULL, вам все одно не потрібно буде дзвонити fill_foo. fill_foo перевіряє, чи має покажчик значення, а не чи покажчик має дійсне значення. У C ++ вказівники не гарантують, що вони мають бути NULL або мають дійсне значення.
Містер Лістер

4
Тут кращим рішенням може стати твердження (). Немає сенсу намагатися "бути в безпеці". Якщо NULL був переданий, це, очевидно, неправильно, то чому б не просто чітко вийти з ладу, щоб зрозуміти програміста повністю? (А у виробництві це не має значення, тому що ви довели, що ніхто не зателефонує fill_foo () з NULL, правда? Дійсно, це не так складно.)
Ambroz Bizjak

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

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

7

Інші відповіді в значній мірі висвітлювали ваше точне запитання. Здійснюється нульова перевірка, щоб переконатися, що отриманий вами вказівник фактично вказує на дійсний екземпляр типу (об'єкти, примітиви тощо).

Я збираюся додати тут свою пораду. Уникайте недійсних перевірок. :) Нульові перевірки (та інші форми оборонного програмування) захаращують код і фактично роблять його більш схильним до помилок, ніж інші методи поводження з помилками.

Моя улюблена техніка, що стосується вказівників об’єкта, - це використовувати шаблон Null Object . Це означає повернення (покажчик - а ще краще, посилання на) порожній масив або список замість нуля, або повернення порожнього рядка ("") замість нуля, або навіть рядок "0" (або щось еквівалентне "нічого "у контексті), де ви очікуєте, що воно буде розібране на ціле число.

Як бонус, ось дещо, що ви, можливо, не знали про нульовий покажчик, який був (вперше офіційно) реалізований CAR Hoare для мови Algol W у 1965 році.

Я називаю це моєю помилкою в мільярд доларів. Це був винахід нульової посилання в 1965 році. Тоді я розробляв першу комплексну систему для посилань на об'єктно-орієнтованій мові (ALGOL W). Моя мета полягала в тому, щоб будь-яке використання посилань було абсолютно безпечним, а перевірка виконувалась компілятором автоматично. Але я не втримався від спокуси ввести нульову посилання просто тому, що це було так просто здійснити. Це призвело до незліченних помилок, уразливості та збоїв у системі, які, ймовірно, спричинили біль і шкоду в мільярд доларів за останні сорок років.


6
Null Object навіть гірше, ніж просто мати нульовий покажчик. Якщо алгоритм X вимагає даних Y, яких у вас немає, то це помилка у вашій програмі , яку ви просто приховуєте, роблячи вигляд, що це робите.
DeadMG

Це залежить від контексту, і будь-який спосіб тестування на "наявність даних" б'є тестування на нуль у моїй книзі. З мого досвіду, якщо алгоритм працює над, скажімо, списком, а список порожній, то алгоритм просто не має нічого спільного, і він досягає цього, використовуючи стандартні оператори управління, такі як for / foreach.
Ям Маркович

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

@DeadMG Оскільки програми стосуються введення даних, а в реальному світі, на відміну від домашніх завдань, введення даних може бути неактуальним (наприклад, порожнім). Код все ще називається в будь-якому випадку. У вас є два варіанти: або ви перевіряєте на відповідність (або порожнечу), або розробляєте свої алгоритми так, щоб вони читали і працювали добре, не чітко перевіряючи відповідність, використовуючи умовні заяви.
Ям Маркович

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

4

Значення нульового вказівника являє собою чітко визначене "ніде"; це недійсне значення вказівника, яке гарантується порівнянням нерівним з будь-яким іншим значенням вказівника. Спроба перенаправити нульовий вказівник призводить до невизначеної поведінки, і, як правило, призведе до помилки виконання, тому ви хочете переконатися, що покажчик не є NULL, перш ніж намагатися його знеструмити. Ряд функцій бібліотеки C і C ++ поверне нульовий покажчик, що вказує на стан помилки. Наприклад, функція бібліотеки mallocповерне нульове значення вказівника, якщо вона не може виділити кількість байтів, які були запитувані, а спроба доступу до пам'яті через цей покажчик (як правило) призведе до помилки виконання:

int *p = malloc(sizeof *p * N);
p[0] = ...; // this will (usually) blow up if malloc returned NULL

Тому нам потрібно переконатися, що mallocвиклик вдався, перевіривши значення pна NULL:

int *p = malloc(sizeof *p * N);
if (p != NULL) // or just if (p)
  p[0] = ...;

Тепер, покладись на шкарпетки хвилину, це стане трохи кумедним.

Існує значення нульового вказівника та константа нульового вказівника , і обидві не обов'язково однакові. Покажчик нульовий значення є будь-яке значення , що лежать в основі використання архітектури не уявляють «ніде». Це значення може бути 0x00000000, або 0xFFFFFFFF, або 0xDEADBEEF, або щось зовсім інше. Не думайте , що покажчик нульового значення завжди 0.

Константа нульового покажчика , OTOH, завжди є 0 -значним інтегральним виразом. Що стосується вашого вихідного коду , 0 (або будь-який інтегральний вираз, який оцінює до 0), являє собою нульовий покажчик. І C, і C ++ визначають макрос NULL як константа нульового покажчика. Коли ваш код складений, константа нульового вказівника буде замінена на відповідне значення нульового вказівника у створеному машинному коді.

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

int *p;

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

Зауважте, що це більше проблема C, ніж C ++; ідіоматичний C ++ не повинен так активно використовувати покажчики.


3

Існує пара методів, всі по суті роблять те саме.

int * foo = NULL; // інколи встановлюється 0x00 або 0 або 0L замість NULL

null check (перевірте, чи вказівник є null), версія A

if (foo == NULL)

нульова перевірка, версія B

якщо (! foo) // оскільки NULL визначено як 0,! foo поверне значення з нульового вказівника

нульова перевірка, версія C

якщо (foo == 0)

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


2

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


1
@James: 'новий' у режимі ядра?
Неманя Трифунович

1
@James: реалізація C ++, яка представляє можливості, якими користується значна частина кодерів C ++. Це включає всі функції мови C ++ 03 (за винятком export) та всі функції бібліотеки C ++ 03 та TR1 та гарний фрагмент C ++ 11.
DeadMG

5
Я б хотів, щоб люди не казали, що "посилання гарантують невідповідність". Вони ні. Генерувати нульову посилання так само просто, як і нульовий вказівник, і вони поширюються аналогічно.
mjfgates

2
@Stargazer: Питання на 100% зайве, коли ви просто використовуєте інструменти так, як мовні дизайнери та добра практика пропонують вам.
DeadMG

2
@DeadMG, не має значення, чи вона зайва. Ви не відповіли на запитання . Скажу ще раз: -1.
riwalk

-1

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

Багато постачальників програмного забезпечення, такі як Microsoft, Oracle, Adobe, Apple ... випускають програмний патч, щоб виправити ці вразливості безпеки. Я думаю, ви повинні перевірити значення NULL кожного вказівника :)

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