Перевірка на покажчик NULL в C / C ++ [закрито]


153

В останньому огляді коду дописувач намагається забезпечити виконання всіх NULLперевірок покажчиків таким чином:

int * some_ptr;
// ...
if (some_ptr == NULL)
{
    // Handle null-pointer error
}
else
{
    // Proceed
}

замість

int * some_ptr;
// ...
if (some_ptr)
{
    // Proceed
}
else
{
    // Handle null-pointer error
}

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

if (some_ptr = NULL)

що просто абсолютний біль для пошуку та налагодження.

Який спосіб ви віддаєте перевагу і чому?


32
Мати послідовний стиль іноді важливіше, ніж обраний стиль.
Марк Викуп

15
@Mark: Послідовність завжди є найважливішим фактором вашого стилю.
sbi

13
"Також я вважаю, що другий метод має менші шанси ввести помилку ilk: якщо (some_ptr = NULL)" Ось чому деякі користуються, якщо (NULL == some_ptr), не можуть призначити NULL, так що ви отримаєте помилка компіляції.
користувач122302

14
Більшість компіляторів сьогодні попереджають про призначення в умовних умовах - на жаль, занадто багато розробників ігнорують попередження компілятора.
Майк Еллері

10
Я не згоден з твердженням вище, що важлива послідовність. Це не має нічого спільного з послідовністю; не добрий сорт все одно. Це сфера, де ми повинні дозволити програмістам висловити власний стиль. Якщо є людина, яка не розуміє відразу жодної форми, вона повинна негайно перестати читати код і розглянути можливість зміни кар'єри. Я скажу більше: Якщо є косметична консистенція, яку ви не можете автоматично застосувати до сценарію, видаліть його. Вартість виснаження людської моралі ШЛЯХО важливіша, ніж ті дурні рішення, які люди приймають на зустрічі.
wilhelmtell

Відповіді:


203

На мій досвід, переважні тести форми if (ptr)або if (!ptr). Вони не залежать від визначення символу NULL. Вони не піддають можливості випадкового призначення. І вони чіткі та лаконічні.

Редагувати: Як в коментарі вказує SoapBox, вони сумісні з класами C ++, такими як auto_ptrоб'єкти, які виступають вказівниками і які забезпечують перетворення, boolщоб увімкнути саме цю ідіому. Для цих об'єктів явне зіставлення NULLповинно викликати перетворення на покажчик, який може мати інші смислові побічні ефекти або бути дорожчим, ніж проста перевірка існування, що має на boolувазі перетворення.

У мене є перевага до коду, який говорить, що це означає без зайвого тексту. if (ptr != NULL)має те саме значення, if (ptr)але ціною надмірної специфіки. Наступна логічна річ - написати, if ((ptr != NULL) == TRUE)і таким чином лежить божевілля. Мова С ясно , що логічне значенням проходить перевірку if, whileабо тому подібні має конкретного змісту ненульового значення істинно і нуль є хибним. Надмірність не робить це зрозумілішим.


23
Крім того, вони сумісні з класами обгортки вказівників (shared_ptr, auto_ptr, scoped_tr тощо), які зазвичай переосмислюють bool оператора (або safe_bool).
SoapBox

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

2
@Bryan, я поважаю це, і якщо з'явиться відповідь "кращого", будь ласка, сміливо пересувайте галочку за потребою. Щодо суб'єктивності, так, це суб'єктивне. Але це принаймні суб'єктивне обговорення, коли є об'єктивні питання, які не завжди очевидні, коли постає питання. Лінія розмита. І суб’єктивні ... ;-)
RBerteig

1
Одне важливе, що слід зазначити: до недавнього часу я виступав за те, щоб написати "if (ptr)" замість "if (ptr! = NULL)" або "if (ptr! = Nullptr)", як це більш стисло, тому це код читабельніший. Але я щойно з’ясував, що порівняння підвищує (відповідно) попередження / помилку для порівняння з NULL / nullptr на останніх компіляторах. Тож якщо ви хочете перевірити покажчик, краще зробіть "if (ptr! = NULL)" замість "if (ptr)". Якщо ви помилково оголосили ptr як int, перша перевірка підніме попередження / помилку, тоді як остання складе без будь-якого попередження.
Арно

1
@David 天宇 Wong Ні, це не те, що малося на увазі. Приклад на цій сторінці - це послідовність двох рядків, int y = *x; if (!x) {...}де оптимізатору дозволено міркувати, що оскільки *xбуло б невизначено, якщо xNULL, воно не повинно бути NULL, а значить, !xповинно бути ЛІЖНІМ. Вираз !ptr- це не визначена поведінка. У прикладі, якби тест був зроблений перед будь-яким використанням *x, оптимізатор не міг би усунути тест.
RBerteig


25

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

В C ++

NULL визначається як 0 або 0L в C ++.

Якщо ви читали мову програмування на C ++, Bjarne Stroustrup пропонує 0явно використовувати, щоб уникнути NULLмакросу під час виконання завдань , я не впевнений, чи зробив він це з порівняннями, минув час, коли я читав книгу, я думаю, що він щойно зробив if(some_ptr)без явного порівняння, але я на цьому нечіткий.

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

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

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

if(some_ptr){;}

Це зрозуміло, якщо ви знаєте, що some_ptrце тип вказівника, але це може також виглядати як ціле порівняння:

if(some_ptr != 0){;}

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

if(some_ptr != NULL){;}

У C ++ 0x є nullptr, який зараз є кращим методом, оскільки він є явним і точним, просто будьте уважні щодо випадкового призначення:

if(some_ptr != nullptr){;}

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

В С

С - інший звір.

У C NULL можна визначити як 0 або як ((void *) 0), C99 дозволяє реалізувати визначені нульові константи покажчика. Таким чином, це фактично зводиться до визначення NULL реалізації, і вам доведеться перевірити його у вашій стандартній бібліотеці.

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

З цього погляду я, мабуть, рекомендував би використовувати NULLвизначення макросу в C.


tl; dr, і хоча ви правий 0на C ++, він має значення в C : (void *)0. 0є кращим, ніж NULLв C ++, оскільки помилки типу можуть бути PITA.
Метт Столяр

@Matt: Але NULLвсе одно дорівнює нулю.
GManNickG

@GMan: Не в моїй системі: #define NULL ((void *)0)відlinux/stddef.h
Matt Joiner

@Matt: В C ++. Ви сказали, що 0 є кращим, але NULLмає бути нульовим.
GManNickG

Це хороший момент, я нехтував згадувати це, і вдосконалюю свою відповідь, пропонуючи це. ANSI C може мати NULL, визначений як ((void *) 0), C ++ визначає NULL як 0. Я не перебирав стандарт для цього безпосередньо, але я розумію, що в C ++ NULL може бути 0 або 0L.
M2tM

19

Я використовую if (ptr), але про це зовсім не варто сперечатися.

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

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

Зверніть увагу на C ++ 0x, ми можемо зробити if (ptr == nullptr), що для мене робить читання краще. (Знову ж я ненавиджу макрос. Але nullptrце приємно.) Я все одно це роблю if (ptr), лише тому, що це те, до чого я звик.


сподіваюся, що додаткові 5 років досвіду передумали :) якщо C ++ / C мав оператора на зразок if_exist (), то це було б сенсом.
magulla

10

Чесно кажучи, я не бачу, чому це має значення. Будь-яка з них є цілком зрозумілою, і кожен, хто має досвід C або C ++, повинен розуміти обидва. Хоча один коментар:

Якщо ви плануєте розпізнати помилку і не продовжуєте виконувати функцію (тобто ви збираєтеся кинути виняток або повернути код помилки негайно), вам слід зробити це захисним застереженням:

int f(void* p)
{
    if (!p) { return -1; }

    // p is not null
    return 0;
}

Таким чином ви уникаєте "коду стрілки".


хтось вказав тут kukuruku.co/hub/programming/i-do-not-know-c, що if(!p)не визначена поведінка. Це може працювати чи ні.
Девід 天宇 Вонг

@David 天宇 Вонг, якщо ви говорите про приклад №2 за цим посиланням, if(!p)це не визначена поведінка, це лінія, що знаходиться до цього. І if(p==NULL)була б точно така ж проблема.
Марк Викуп

8

Особисто я завжди використовував, if (ptr == NULL)тому що це робить мій намір явним, але на даний момент це просто звичка.

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

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


7

Ще один момент на користь foo == NULLпрактики: Якщо fooце, скажімо, а, int *або а bool *, то if (foo)чек може випадково трактуватися читачем як тестування значення пункту, тобто як if (*foo). NULLПорівняння тут є нагадуванням про те, що ми говоримо про покажчику.

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


4

Насправді я використовую обидва варіанти.

Бувають ситуації, коли ви спочатку перевіряєте правильність вказівника, і якщо він NULL, ви просто повертаєтесь / виходите з функції. (Я знаю, що це може призвести до дискусії "якщо функція має лише одну точку виходу")

Більшу частину часу ви перевіряєте вказівник, потім робите те, що хочете, а потім вирішуєте випадок помилок. Результатом може стати некрасивий x-раз відступний код з декількома if.


1
Я особисто ненавиджу код стрілки, який призведе до використання однієї точки виходу. Чому б не вийти, якщо я вже знаю відповідь?
ruslik

1
@ruslik: у C (або стилі C + з класами C ++) наявність декількох повернень ускладнює очищення. У "справжньому" C ++ це, очевидно, не проблема, оскільки очищення обробляється RAII, але деякі програмісти (з більш-менш вагомих причин) застрягли в минулому, і або відмовляються, або не дозволяють покладатися на RAII .
jalf

@jalf в таких випадках gotoможе бути дуже зручним. Також у багатьох випадках malloc()те, що потребує очищення, може бути замінено більш швидким alloca(). Я знаю, що вони не рекомендуються, але вони існують з причини (я вважаю, що добре названа мітка для gotoнабагато чистіша за затуманене if/elseдерево).
ruslik

1
allocaнестандартний і абсолютно не безпечний. Якщо це не вдасться, ваша програма просто підірветься. Немає шансів на відновлення, а оскільки стек порівняно невеликий, можливий збій. У разі відмови, можливо, ви згортаєте купу і вводите вразливості, що компрометують привілеї. Ніколи не використовуйте allocaабо vlas, якщо ви не обмежите розмір, який буде виділено (і тоді ви можете просто використовувати звичайний масив).
R .. GitHub СТОП ДОПОМОГА ВІД

4

Мова програмування на C (K&R) дозволить вам перевірити наявність null == ptr, щоб уникнути випадкового призначення.


23
K&R також запитає "що таке розумний вказівник?" У світі С ++ все змінюється.
Олексій Ємельянов

2
а точніше, змінилися речі вже у світі C ++ ";)
jalf

3
Де K&R заявляє, що (посилання)? Вони ніколи не використовують цей стиль самі. У розділі 2 K&R2 вони навіть рекомендують if (!valid)вище if (valid == 0).
знімок

6
Влаштуйся, люди. Він сказав k & r, а не K&R. Вони, очевидно, менш відомі, оскільки їхні ініціали навіть не мають великих літер.
jmucchiello

1
капіталізація просто сповільнює вас
Дерек

3

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

І не те, що це має значення, але мої особисті переваги є if (ptr). Сенс для мене більш очевидний, ніж навіть if (ptr == NULL).

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

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

if (error_condition)
  bail_or_fix();
  return if not fixed;

// If I'm still here, I'm on the happy path

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

Але якщо це не в посібнику зі стилів, то це лише моя думка, і ваша думка так само справедлива. Або стандартизувати, або ні. Не дозволяйте рецензенту псевдостандартизувати лише тому, що він має думку.


1

Це одна з основ обох мов, які покажчики оцінюють на тип і значення, які можна використовувати як керуючий вираз, boolв C ++ і intC. Просто використовуйте його.


1
  • Покажчики не булеві
  • Сучасні компілятори C / C ++ видають попередження, коли ви пишете if (foo = bar)випадково.

Тому я віддаю перевагу

if (foo == NULL)
{
    // null case
}
else
{
    // non null case
}

або

if (foo != NULL)
{
    // non null case
}
else
{
    // null case
}

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

Переконайтеся, що ви зробили нульову перевірку вказівника.


2
Це правда, що покажчики не булеві, але в C, якщо-заяви не приймають булевих: вони приймають цілі вирази.
Кен

@Ken: це тому, що C порушено в цьому відношенні. Концептуально це булевий вираз і (на мою думку) слід ставитися до цього.
JeremyP

1
У деяких мовах є if-заяви, які перевіряють лише на null / not-null. У деяких мовах є if-заяви, які випробовують лише ціле число на знак (триходовий тест). Я не бачу причин вважати С "зламаним", оскільки вони обрали іншу концепцію, яка вам подобається. Я багато чого не люблю щодо C, але це просто означає, що модель програми C не є такою ж, як моя ментальна модель, не те, що жоден із нас (я чи С) зламаний.
Кен

@Ken: Логічне не є числом або покажчиком концептуально , ніколи розум, мова.
JeremyP

1
Я не сказав, що булевим числом є чи покажчик. Я сказав, що немає жодних підстав наполягати на тому, що if-заява повинна або може мати лише булевий вираз, і запропонувала контрприклади на додаток до того, що знаходиться в руці. Багато мов займають щось інше, ніж булеве, а не лише "нульовим / ненульовим" способом C. В обчислювальних технологіях наявність твердження if, яке приймає (лише) булеве, є порівняно недавньою розробкою.
Кен

1

Я великий шанувальник того , що C / C ++ не перевіряє типи в логічних умовах if, forі whileзаяві. Я завжди використовую таке:

if (ptr)

if (!ptr)

навіть на цілі числа чи інший тип, який перетворюється на bool:

while(i--)
{
    // Something to do i times
}

while(cin >> a >> b)
{
    // Do something while you've input
}

Кодування в цьому стилі для мене більш зрозуміле і зрозуміліше. Просто моя особиста думка.

Нещодавно, працюючи над мікроконтролером OKI 431, я помітив, що наступне:

unsigned char chx;

if (chx) // ...

є більш ефективним, ніж

if (chx == 1) // ...

тому що в подальшому випадку компілятору доводиться порівнювати значення chx до 1. Де chx - це просто істинний / хибний прапор.


1

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

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