Используете ли вы 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

Вот что говорит Страуструп: FAQ по стилю и технике C ++

В C ++ определение NULLравно 0, поэтому есть только эстетическое различие. Я предпочитаю избегать макросов, поэтому я использую 0. Другая проблема в NULLтом, что люди иногда ошибочно полагают, что он отличается от 0 и / или не является целым числом. В стандартном коде NULLиногда определялось что-то неподходящее, и поэтому его следует избегать. Это менее распространено в наши дни.

Если вам нужно назвать нулевой указатель, вызовите его nullptr; это то, что называется в C ++ 11. Тогда nullptrбудет ключевое слово.

Тем не менее, не парься мелочи.


7
Бьярне написал это до того, как 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и сохраните его в полной безопасности.
Фнието - Фернандо Ньето

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

37

Я всегда использую:

  • NULL для указателей
  • '\0' для персонажей
  • 0.0 для поплавков и парных разрядов

где 0 будет хорошо. Это вопрос сигнализации намерения. Тем не менее, я не анальный об этом.


25
я , вероятно , следует использовать 0.0f для поплавков, чтобы избежать неявного приведение типа
EvilTeach

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(в соответствии с часто задаваемыми вопросами Страуструпа: должен ли я использовать 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. в 64-битной версии 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.
Джон Хансон

2
«Не нравятся макросы» - это странная критика объектно-подобного #define. Может быть, вы хотите сказать, что вам не нравится C-препроцессор?
Андрей Прок

@ Андрей - Единственное преимущество NULLнад (type *)0является возможность поиска, как мне кажется. В противном случае кажется ненужным запутывание, если бы это не было языком C. Я лично думаю, что идиома распространения NULLповсеместно заслуживает смерти. NULLэто бесполезный макрос на мой взгляд. Бритве Оккама нужно здесь
поработать

11

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

char *p;

...

if (p) { ... }

не будет работать, как вы ожидаете. Вы должны были написать

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

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


26
Вы не использовали совместимый компилятор. Стандарт говорит, что NULL равен 0 и что компилятор должен преобразовать 0 в контексте указателя в правильное истинное значение NULL для арки.
Эван Теран

17
Да ты прав. Это было в середине 80-х годов, прежде чем ANSI выпустила стандарт C. Тогда не было такого понятия, как соответствие, и авторы компиляторов могли свободно интерпретировать язык так, как они считали нужным. Вот почему стандарт был необходим.
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 и все остальные означают одно и то же, но являются разными логическими объектами в вашей программе. Они должны быть использованы как таковые. NULL - это указатель, 0 - это количество, 0x0 - это значение, чьи биты интересны и т. Д. Вы не назначали бы '\ 0' для указателя, независимо от того, компилируется он или нет.

Я знаю, что некоторые сообщества поощряют демонстрацию глубоких знаний об окружающей среде, нарушая условия окружающей среды. Ответственные программисты, однако, создают поддерживаемый код и не допускают такой практики в свой код.


5

Странно, никто, включая Страуструп, не упомянул это. В то время как много говорить о стандартах и эстетику никто не заметил , что это опасно использовать 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

На этом я с Страуструпом :-) Так как 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. Не по какой-либо реальной причине, просто когда я впервые изучал C ++, я прочитал что-то, что рекомендовало использовать 0, и я просто всегда делал это таким образом. В теории может возникнуть путаница с удобочитаемостью, но на практике я никогда не сталкивался с такой проблемой в тысячах человеко-часов и миллионах строк кода. Как говорит Страуструп, на самом деле это вопрос личной эстетики, пока стандартом не станет 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;

для чисто виртуальной функции в первый раз. Я думал, что это будет волшебная спячка на неделю. Когда я понял, что это просто установка указателя функции на a null(поскольку виртуальные функции - это просто указатели функций в большинстве случаев для C ++), я пнул себя.

virtual void DrawTo(BITMAP *buffer) =null;

было бы менее запутанным, чем это взволнование без правильного расстояния между моими новыми глазами. На самом деле, мне интересно, почему C ++ не использует строчные буквы так nullже, как он использует строчные ложные и истинные сейчас.


В общем, я предпочитаю NULl 0 для указателей. Однако '= 0;' это идиоматический способ объявить чисто виртуальную функцию в C ++. Я настоятельно рекомендую вам не использовать '= NULL;' для этого конкретного случая.
Данио

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