Чи використовуєте ви NULL або 0 (нуль) для покажчиків у C ++?


194

У перші дні C ++, коли він був закріплений поверх C, ви не можете використовувати NULL, як це було визначено як (void*)0. Ви не можете призначити NULL жодному вказівнику, окрім цього void*, який зробив його марним. Ще в ті часи було прийнято, що ти користувався0 (нуль) для нульових покажчиків.

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

if (p && !q)
  do_something();

тоді використання нуля має більше сенсу (як якщо ви використовуєте NULL, ви не можете логічно використовувати p && !q- вам потрібно явно порівнювати NULL, якщо ви не вважаєте, що NULLце нуль, і в цьому випадку, чому використовуватиNULL ).

Чи є якась об’єктивна причина віддати перевагу нулю над NULL (або навпаки), або все це лише особисті переваги?

Редагувати: Я повинен додати (і мався на увазі спочатку), що за допомогою RAII та винятків я рідко використовую нульові / NULL покажчики, але іноді вони все ще потрібні.


9
зачекайте, чи не потрібний нульовий покажчик оцінюється як хибний незалежно від того, чи є нуль внутрішньо нульовим чи ні?
Mooing Duck

Відповіді:


186

Ось ось що Stroustrup взяв на себе таке: C ++ FAQ та стиль та техніка

У C ++ визначення NULL0 дорівнює, тому є лише естетична різниця. Я вважаю за краще уникати макросів, тому використовую 0. Інша проблема NULLполягає в тому, що люди іноді помилково вважають, що він відрізняється від 0 та / або не ціле число. У стандартному коді,NULL було / іноді визначається щось непридатне, а тому довелося / слід уникати. Це рідше в ці дні.

Якщо вам потрібно назвати нульовий покажчик, зателефонуйте йому nullptr; саме так воно називається в C ++ 11. Тоді, nullptrбуде ключове слово.

Це сказало, не потійте дрібні речі.


7
Bjarne написав це ще до того, як C ++ 0x почав працювати над новим нульовим типом. Так буде, що NULL буде використовуватися для цього типу, коли він буде доступний для платформи, і я думаю, ви побачите C-зміну в загальному консенсусі щодо цього.
Річард Корден

122

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

  1. Документація наміру

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

  1. Перевантаження вказівника та 'int' порівняно рідко

Приклад, який всі цитують:

void foo(int*);
void foo (int);

void bar() {
  foo (NULL);  // Calls 'foo(int)'
}

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

  1. Інструменти аналізу можуть допомогти сьогодні!

Навіть за відсутності C ++ 0x, сьогодні є інструменти, які підтверджують, що NULLвін використовується для покажчиків, і що 0він використовується для цілісних типів.

  1. C ++ 11 матиме новий std::nullptr_tтип.

Це найновіший аргумент до таблиці. Проблема 0і NULLактивно вирішується для C ++ 0x, і ви можете гарантувати, що для кожної реалізації, яка надається NULL, найперше, що вони зроблять, це:

#define NULL  nullptr

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


1
Я мушу визнати, що це досить непогані моменти. Я радий, що C ++ 0x матиме нульовий тип, я думаю, що це зробить багато матеріалів чистішими.
Роб

2
@ Річард, чому б не зробити навпаки? Ви можете використовувати Meyers nullptr_t, тоді, коли 0x стане доступним, ви виймете #includeта збережете в безпечному боці весь шлях.
fnieto - Фернандо Нієто

15
#define NULL nullptrздається небезпечним. На краще чи гірше, багато застарілих кодів використовує NULL для речей, відмінних від 0. Наприклад, ручки часто реалізуються як певний цілісний тип, і встановлення їх не NULLє рідкістю. Я навіть бачив зловживання на зразок використання NULLдля встановлення charнульового термінатора.
Адріан Маккарті

8
@AdrianMcCarthy: Я б сказав, що це було небезпечно лише в тому випадку, якщо існує небезпека того, що код мовчки складеться і мати інше значення. Я майже впевнений, що це не так, тому насправді всі неправильні способи використання NULL будуть виявлені.
Річард Корден

3
@RichardCorden: Гм, це передбачає, що ті інші способи використання NULLнасправді неправильні. Багато API вже давно використовуються NULLз ручками, і це фактично документальне використання багатьох з них. Не прагматично раптово їх порушувати і заявляти, що вони роблять неправильно.
Адріан Маккарті

45

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


28
0 не є детальною інформацією про реалізацію. Стандарт визначає 0 як будь-який бітовий шаблон представляє нульовий покажчик.
Ферруччо

5
Ніби ..!! Чувак, C ++ - мова низького рівня! Використовуйте 0, це добре відома ідіома.
hasen

8
Я розумію, що це частина стандарту. Це деталь реалізації, що стосується читання коду. Читач повинен думати, що "покажчик NULL" не "0, що в даному випадку означає вказівник NULL, а не число, з яким я міг би робити арифметику".
Енді Лестер

2
+1. Домовився з Енді. @Ferruccio, Деталізація ідеї ідеї програміста не така, як визначено
користувач

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

38

Я завжди використовую:

  • NULL для покажчиків
  • '\0' для символів
  • 0.0 для поплавців та пар

де 0 було б добре. Це питання про наміри сигналізації. Це сказало: я не анальний до цього.


25
вам, ймовірно, слід використовувати 0.0F для поплавків, щоб уникнути неявного
набору тексту

35

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

Щодо нових проектів, я помістив це в заголовок проекту:

static const int nullptr = 0;

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


4
Використання NULL буде більш портативним довгостроково. 'nullptr' буде доступний для деяких платформ, а не для інших. Ваше рішення тут вимагає використання препроцесора навколо вашої декларації, щоб переконатися, що він присутній лише за потреби. NULL зробить це автоматично.
Річард Корден

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

4
Крім того, ви завжди можете #define nullptr NULL для свого компілятора, який не стосується C ++ 0x.
Антеру

20
    cerr << sizeof(0) << endl;
    cerr << sizeof(NULL) << endl;
    cerr << sizeof(void*) << endl;

    ============
    On a 64-bit gcc RHEL platform you get:
    4
    8
    8
    ================

Мораль розповіді. Ви повинні використовувати NULL під час роботи з покажчиками.

1) Він оголошує ваш намір (не змушуйте мене шукати весь ваш код, намагаючись зрозуміти, чи є змінною покажчик чи якийсь числовий тип).

2) У певних викликах API, які очікують змінних аргументів, вони використовуватимуть NULL-покажчик для позначення кінця списку аргументів. У цьому випадку використання "0" замість NULL може спричинити проблеми. На 64-розрядної платформі виклик va_arg хоче 64-розрядний вказівник, але ви пройдете лише 32-бітове ціле число. Мені здається, ви покладаєтесь на інші 32-бітові для вас нулі? Я бачив певні компілятори (наприклад, icpc від Intel), які не такі вибагливі - і це призвело до помилок під час виконання.


NULLможливо, не є портативним і не безпечним. Можливо, ще є платформи #define NULL 0(відповідно до поширених запитань Stroustrup: Чи слід використовувати NULL або 0?, Цитовані за головним запитанням, і це один з перших результатів пошуку). Принаймні, у старших C ++ 0має особливе понятійне значення в контексті покажчика. Не варто конкретно думати про шматочки. Також зверніть увагу , що в різних контекстах цілочисельних ( short, int, long long) « sizeof(0)» буде відрізнятися. Я думаю, що ця відповідь трохи помилкова.
FooF

(Особисто я, як програміст C у повсякденному житті, прийшов до цього питання, щоб зрозуміти, чому люди хочуть використовувати NULLзамість цього (char *)0, (const char *)0або (struct Boo *)0чи (void *)0або що інше, щоб виразити наміри чіткіше - не будучи (на мою думку) занадто громіздким.)
FooF

Голосувати. це відбувається у компіляторі msvc2013 C. в 64bit, 0 при перетворенні на покажчик не гарантує показник NULL.
pengMiao

16

Якщо я пам'ятаю правильно, NULL визначається по-різному в заголовках, якими я користувався. Для C він визначається як (void *) 0, а для C ++ - лише 0. Код виглядав приблизно так:

#ifndef __cplusplus
#define NULL (void*)0
#else
#define NULL 0
#endif

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

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

Наприклад, вважають за краще використовувати:

if (pointer_value != NULL || integer_value == 0)

а не:

if (pointer_value || !integer_value)

Досить сказати, що це все виправлено в C ++ 11, де можна просто використовувати nullptrзамість NULL, а також nullptr_tце тип a nullptr.


15

Я б сказав, що історія говорила, і ті, хто стверджував на користь використання 0 (нуля), помилялися (включаючи Б'ярна Струструпа). Аргументами на користь 0 були переважно естетика та "особисті переваги".

Після створення C ++ 11, з його новим типом nullptr, деякі компілятори почали скаржитися (з параметрами за замовчуванням) щодо передачі 0 функціям з аргументами вказівника, оскільки 0 не є покажчиком.

Якби код був написаний за допомогою NULL, простий пошук і заміна могли бути виконані через кодову базу, щоб зробити його nullptr замість цього. Якщо ви застрягли в коді, написаному з використанням вибору 0 в якості вказівника, оновити його набагато складніше.

І якщо вам зараз потрібно написати новий код до стандарту C ++ 03 (і не можете використовувати nullptr), вам дійсно слід просто використовувати NULL. Це значно полегшить вам оновлення в майбутньому.


11

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

Ви можете використовувати об'єкт nullptr, запропонований Скоттом Майєрсом та іншими, поки C ++ не отримає ключове слово nullptr:

const // It is a const object...
class nullptr_t 
{
public:
    template<class T>
    operator T*() const // convertible to any type of null non-member pointer...
    { return 0; }

    template<class C, class T>
    operator T C::*() const   // or any type of null member pointer...
    { return 0; }

private:
    void operator&() const;  // Can't take address of nullptr

} nullptr = {};

Google "nullptr" для отримання додаткової інформації.


9
Будь-яка стороння бібліотека, яка визначає NULL до будь-якого іншого, ніж 0 (або (void*)0якщо вона складена як код C), просто задає проблеми і не повинна використовуватися.
Адам Розенфілд

2
Ви коли-небудь насправді бачили бібліотеку, яка переосмислює NULL? Колись? Якщо така бібліотека коли-небудь існувала, у вас виникли б більші проблеми, ніж переосмислений NULL, наприклад, ви використовуєте бібліотеку, яка є достатньо німою для переосмислення NULL.
Енді Лестер

1
Більше десятиліття тому я смутно пам'ятаю, що мали справу з сторонніми заголовками, можливо, Orbix або ObjectStore, які визначали NULL. Я думаю, що у мене є патологічна ненависть до макросів після того, як витрачатися кілька днів і ночей, намагаючись змусити різні сторонні заголовки працювати з windows.h.
jon-hanson

2
"не люблять макроси" - це дивна критика об'єкта, схожого на #define. Можливо, ви хочете сказати, що вам не подобається C-препроцесор?
Ендрю Прок

@Andrew - Мені здається, єдина перевага NULLнад пошуком (type *)0. Інакше здається непотрібним заплутаність, якби це не ідіома С. Я особисто вважаю, що ідіома поширення NULLв усьому світі заслуговує на смерть. NULLна мій погляд, марний макрос Бритва Оккама має тут щось зробити ...
FooF

11

Я колись працював на машині, де 0 було дійсною адресою, а NULL було визначено як спеціальне вісімкове значення. На цій машині (0! = NULL), таким кодом, як

char *p;

...

if (p) { ... }

не працюватиме так, як ви очікуєте. ВАМ ТИ писати

if (p != NULL) { ... }

Хоча я вважаю, що більшість компіляторів визначають NULL як 0 в наші дні, я все ще пам’ятаю урок тих років тому: NULL не обов'язково 0.


26
Ви не використовували сумісний компілятор. Стандарт каже, що NULL дорівнює 0, і що компілятор повинен перетворити 0 у контексті покажчика у правильне справжнє значення NULL для арки.
Еван Теран

17
Так, ти маєш рацію. Це було в середині 80-х, перш ніж ANSI випустив стандарт С. Тоді не було такого дотримання вимог, і письменники-упорядники могли вільно інтерпретувати мову так, як вважали за потрібне. Тому необхідний був стандарт.
mxg

9

Я думаю, що стандарт гарантує, що NULL == 0, тому ви можете зробити будь-яке. Я вважаю за краще NULL, оскільки він документує ваш намір.


Якщо у вас є вкладені структури, я думаю, що вислів foo.bar_ptr = (Bar *) 0виражає наміри набагато чіткіше, ніж foo.bar_ptr = NULL. Ця звичка також дає можливість компілятору вловлювати помилки помилок для вас. Для мене foo.bar_ptr = 0виражає наміри, а також використовувати, NULLякщо я знаю, що foo.bar_ptrце вказівник.
FooF

9

Використання або 0, або NULL матиме однаковий ефект.

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

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

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


5

Дивно, про це ніхто не згадував, в тому числі і Stroustroup. У той час як багато говорити про стандарти і естетику ніхто НЕ помітив , що це небезпечно використовувати 0в NULLзамість «s, наприклад, в списку змінних аргументів по архітектурі де sizeof(int) != sizeof(void*). Як і Stroustroup, я віддаю перевагу 0з естетичних причин, але треба бути обережним, щоб не використовувати його там, де його тип може бути неоднозначним.


І в цих небезпечних місцях ви можете використовувати при 0умови , необхідно вказати , які 0ви маєте в виду - наприклад (int *)0, (char *)0, (const char *)0або (void *)0чи (unsigned long long) 0або будь-який інший . Це, на мій погляд, виражає наміри набагато чіткіше, ніж NULL.
FooF

1
Зрозуміло, якщо ви не знаєте, що NULLозначає.
Майкл Крелін - хакер

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

Ну, ось чому кастинг (void*)був абстрагований NULL. І NULLнасправді це виражає наміри досить чітко більшу частину часу. І я думаю, що ваш спогад неправильний. Не впевнений у стандартах, але на практиці я вважаю, що так було (void*)0. І так, nullptrце приємний затворник, хоча це те NULLсаме - вказуючи нульовий покажчик без зазначення типу.
Майкл Крелін - хакер

1
@FooF, на деяких платформах - можливо. У моїй реальності це спрацювало, і тому я підозрюю, що це було визначено як вказівник. Щодо надійності, так, те, що я намагався сказати, що використання nullptrведмедів несе те саме повідомлення NULL, що й стосовно висловлення намірів, про які ви згадали на самому початку. (Попередня обробка NULLсучасних gccурожаїв __null, якою б вона не була).
Майкл Крелін - хакер

4

Я намагаюся уникати цілого питання, використовуючи посилання на C ++, де це можливо. Швидше ніж

void foo(const Bar* pBar) { ... }

ви, можливо, часто зможете писати

void foo(const Bar& bar) { ... }

Звичайно, це не завжди працює; але нульові вказівники можна використовувати надмірно.


3

Я зі Stroustrup на цьому :-) Оскільки NULL не є частиною мови, я вважаю за краще використовувати 0.


3

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

void *ptr = &something;
/* lots o' code */
ptr = NULL; // more obvious that it's a pointer and not being used

IIRC, стандарт не вимагає, щоб NULL був 0, тому використання того, що визначено в <stddef.h>, мабуть, найкраще для вашого компілятора.

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


3

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

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

C ++ 09 додасть конструкцію nullptr, яку я вважаю давно назрілою.


1

Я завжди використовую 0. Не з будь-якої реальної продуманої причини, просто тому, що коли я вперше вивчив С ++, я прочитав щось, що рекомендувало використовувати 0, і я завжди завжди робив це таким чином. Теоретично може виникнути проблема плутанини в читанні, але на практиці я жодного разу не стикався з такою проблемою у тисячах людей-годин і мільйонах рядків коду. Як каже Stroustrup, це дійсно лише особисте естетичне питання, поки стандарт не стане nullptr.


1

Хтось сказав мені один раз ... Я збираюся переосмислити NULL до 69. З тих пір я його не використовую: P

Це робить ваш код досить вразливим.

Редагувати:

Не все в стандарті ідеально. Макрос NULL - визначена реалізацією нульова константа вказівника C ++, не повністю сумісна з макросом C NULL, що крім типу приховування неявного перетворення, перетворює його в непотрібний і схильний до помилок інструмент.

NULL поводиться не як нульовий покажчик, а як буква O / OL.

Скажіть, наступний приклад не бентежить:

void foo(char *); 
void foo(int); 
foo(NULL); // calls int version instead of pointer version! 

Через те, що в новому стандарті з'являється std :: nullptr_t

Якщо ви не хочете чекати нового стандарту і хочете використовувати nullptr, використовуйте принаймні гідний, як запропонований Майєрсом (див. Коментар jon.h).


5
NULLє чітко визначеною частиною стандарту C ++. Дозволити людям, які люблять перевизначати стандартні макроси, редагувати код у вашому проекті, робить ваш код "вразливим"; використання NULLне робить.
CB Bailey

1

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

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

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

Більше завжди є краща альтернатива.


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

-4

Встановити покажчик на 0 просто не так однозначно. Особливо, якщо ви знайдете мову, відмінну від C ++. Сюди входить C, а також Javascript.

Нещодавно я дельтував такий код, як:

virtual void DrawTo(BITMAP *buffer) =0;

для чистої віртуальної функції вперше. Я думав, що це буде якийсь чарівний джибержаш на тиждень. Коли я зрозумів, що це в основному встановлення вказівника функції на null(оскільки віртуальні функції - це лише покажчики функцій у більшості випадків на C ++), я відбив себе.

virtual void DrawTo(BITMAP *buffer) =null;

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


Взагалі я віддаю перевагу NULl до 0 для покажчиків. Однак '= 0;' це ідіоматичний спосіб оголошення чистої віртуальної функції в C ++. Я б настійно радив не використовувати "= NULL;" для цього конкретного випадку.
danio

Це найсмішніший коментар щодо StackOverflow. Ви, мабуть, уже знаєте, що приклад, який ви подали, - це синтаксис чистої віртуальної функції, а не вказівник. І так @danio вірно, ви не повинні використовувати NULL для чистої віртуальної функції.
sgowd
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.