Могу ли я использовать NULL в качестве замены для значения 0?

73

Могу ли я использовать NULLуказатель в качестве замены для значения 0?

Или что-то не так в этом?


Как, например:

int i = NULL;

как замена для:

int i = 0;

В качестве эксперимента я скомпилировал следующий код:

#include <stdio.h>

int main(void)
{
    int i = NULL;
    printf("%d",i);

    return 0;
}

Вывод:

0

Действительно, это дает мне это предупреждение, которое само по себе совершенно правильно:

warning: initialization makes integer from pointer without a cast [-Wint-conversion] 

но результат все равно эквивалентен.


  • Я перехожу в "Неопределенное поведение" с этим?
  • Допустимо ли использовать NULLтаким образом?
  • Что-то не так с использованием NULLв качестве числового значения в арифметических выражениях?
  • И каков результат и поведение в C ++ для этого случая?

Я прочитал ответы о том, в чем разница между NULL, '\ 0' и 0, о том, в чем разница NULL, \0и 0есть, но я не получил оттуда краткую информацию, если она вполне допустима, а также правильно использовать NULLкак значение для работы в присваиваниях и других арифметических операциях.

RobertS поддерживает Монику Челлио
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Самуэль Лью
Было бы лучше задать два отдельных вопроса: один для C и один для C ++.
Конрад Рудольф

Ответы:

82

Могу ли я использовать указатель NULL в качестве замены для значения 0?

Нет , это не безопасно. NULLявляется константой с нулевым указателем, которая может иметь тип int, но более типично имеет тип void *(в C) или иным образом не может быть напрямую назначена int(в C ++> = 11). Оба языка допускают преобразование указателей в целые числа, но они не предусматривают, чтобы такие преобразования выполнялись неявно (хотя некоторые компиляторы предоставляют это как расширение). Более того, хотя для преобразования нулевого указателя в целое число обычно используется значение 0, стандарт не гарантирует этого. Если вам нужна константа с типом intи значением 0, тогда пишите по буквам 0.

  • Могу ли я перейти с неопределенным поведением с этим?

Да, в любой реализации, где NULLрасширяется до значения с типом void *или любого другого, не назначаемого напрямую int. Стандарт не определяет поведение вашего назначения в такой реализации, поэтому его поведение не определено.

  • допустимо ли работать с NULL таким образом?

Это плохой стиль, и он сломается в некоторых системах и при некоторых обстоятельствах. Поскольку вы, похоже, используете GCC, в вашем собственном примере это не получится, если вы скомпилируете эту -Werrorопцию.

  • Что-то не так в использовании NULL в качестве числового значения в арифметических выражениях?

Да. Не гарантируется иметь числовое значение вообще. Если вы имеете в виду 0, то напишите 0, которое не только четко определено, но и короче и понятнее.

  • И как результат в C ++ к этому случаю?

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

Джон Боллинджер
источник
4
Вы должны указать, что «тип, который чаще всего имеет тип void *», относится только к C. void *не является допустимым типом для C ++ (потому что вы не можете назначить void*любой другой тип указателя). В C ++ 89 и C ++ 03 фактически NULL должен иметь тип int, но в более поздних версиях это может быть (и обычно так) nullptr_t.
Мартин Боннер поддерживает Монику
Вы также ошибаетесь, что преобразование void*в intнеопределенное поведение. Не то; это поведение, заданное реализацией.
Мартин Боннер поддерживает Монику
@MartinBonnersupportsMonica, В тех контекстах, где C указывает, что указатель преобразуется в целое число, результат преобразования действительно определяется реализацией, но я не об этом говорю. Это присваивание указателя lvalue целочисленного типа (без явного преобразования через приведение), который имеет неопределенное поведение. Язык не определяет автоматическое преобразование там.
Джон Боллинджер
@MartinBonnersupportsMonica, я отредактировал, чтобы быть более информативным из соображений C ++. В любом случае центральная тема в равной степени применима к обоим языкам: если вам нужно целое число 0, напишите это явно как целочисленную константу соответствующего типа.
Джон Боллинджер
31

NULLнекоторая константа нулевого указателя. В C это может быть целочисленное константное выражение со значением 0или такое выражение, приведенное к void*, причем последнее более вероятно. Это означает, что вы не можете использовать NULLвзаимозаменяемо с нулем. Например, в этом примере кода

char const* foo = "bar"; 
foo + 0;

Замена 0на NULLгарантированно не будет допустимой программой на C, поскольку сложение между двумя указателями (не говоря уже о разных типах указателей) не определено. Это приведет к выдаче диагностики из-за нарушения ограничения. Операнды для сложения не будут действительными .


Что касается C ++, все обстоит несколько иначе. Отсутствие неявного преобразования void*в другие типы объектов означало, что NULLэто исторически определено как 0в коде C ++. В C ++ 03 вам, вероятно, это сойдет с рук. Но начиная с C ++ 11 это может бытьnullptr юридически определено как ключевое слово . Теперь снова выдает ошибку, так как std::nullptr_tне может быть добавлен к типу указателя.

Если NULLопределено как nullptrто даже ваш эксперимент становится недействительным. Там нет преобразования из std::nullptr_tцелого числа. Вот почему он считается более безопасной константой нулевого указателя.

Рассказчик - Unslander Monica
источник
Для полноты 0L также является константой нулевого указателя и может использоваться как NULLв обоих языках.
eerorika
1
@jamesqf Стандарт говорит, что целочисленная константа со значением 0 является константой нулевого указателя. Поэтому 0L является константой нулевого указателя.
eerorika
1
@eerorika: как раз то, что нужно миру, стандарты, которые игнорируют реальность :-). Потому что, если я правильно помню свою сборку 80286, вы даже не можете назначить дальний указатель как одну операцию, поэтому разработчикам компилятора придется -пока это.
jamesqf
2
@jamesqf В соответствии с часто задаваемыми вопросами о C : создание 0константы нулевого указателя: «очевидно, как подпорка для всего существующего плохо написанного кода C, который сделал неверные предположения»
Эндрю
3
@jamesqf, что любая целочисленная константа со значением 0 является константой нулевого указателя (в C) не имеет ничего общего с реализацией аппаратного указателя. Также обратите внимание, что стандарт C не распознает различие между ближним и дальним указателями в любом случае, но поддерживает присваивание указателя к указателю. Он также поддерживает (некоторые) сравнения указателей, которые представляют интересные проблемы для сегментированных форматов адресации, таких как 286.
Джон Боллинджер
21

Могу ли я использовать указатель NULL в качестве замены для значения 0?

int i = NULL;

Правила варьируются между языками и их версиями. В некоторых случаях вы можете, а в других - нет. Независимо от того, вы не должны . Если вам повезет, ваш компилятор предупредит, если вы попытаетесь или, что еще лучше, не скомпилируете.

В C ++ до C ++ 11 (цитата из C ++ 03):

[Lib.support.types]

NULL является константой нулевого указателя C ++, определенной в данном стандарте.

Не имеет смысла использовать константу нулевого указателя в качестве целого числа. Однако...

[Conv.ptr]

Константа нулевого указателя - это целочисленное константное выражение (5.19) r целого типа, которое оценивается как ноль.

Таким образом, это будет технически работать, даже если это бессмысленно. Из-за этой технической возможности вы можете столкнуться с плохо написанными программами, которые злоупотребляют NULL.

Начиная с C ++ 11 (цитата из последнего проекта):

[Conv.ptr]

Константа указателя NULL представляет собой целое число буквальная ([lex.icon]) с нулевым значением или prvalue типа станда :: nullptr_t .

A std​::​nullptr_­tне может быть преобразовано в целое число, поэтому использование в NULLкачестве целого числа будет работать только условно, в зависимости от выбора, сделанного реализацией языка.

PS nullptrэто тип значения std​::​nullptr_­t. Если вам не нужна ваша программа для компиляции до C ++ 11, вы всегда должны использовать nullptrвместо NULL.


C немного отличается (цитаты из проекта C11 N1548):

6.3.2.3 Язык / Конверсии / Другие операнды / Указатели

3 Выражение из целочисленной константы со значением 0 или такое выражение, приведенное к типуvoid * , называется константой нулевого указателя. ...

Таким образом, случай аналогичен описанному в C ++ 11, т. Е. Злоупотребление NULLпроизведениями условно зависит от выбора, сделанного языковой реализацией.

eerorika
источник
10

Да , хотя в зависимости от реализации вам может понадобиться приведение. Но да, это на 100% законно, в противном случае.

Хотя это действительно, действительно, очень плохой стиль (само собой разумеется?).

NULLесть, или был, на самом деле не C ++, это C. Стандарт же впрочем, как и для многих наследий C, имеет два положения ([diff.null] и [support.types.nullptr]) , которые технически сделать NULLC ++. Это определенная реализацией константа нулевого указателя . Поэтому, даже если это плохой стиль, технически он такой же C ++, каким может быть.
Как указано в сноске , возможные реализации могут быть 0или нет 0L, но не могут (void*)0 .

NULLможет, конечно (стандарт прямо не говорит об этом, но это почти единственный выбор, оставшийся после 0или 0L) nullptr. Это почти никогда не бывает, но это законная возможность.

Предупреждение, которое показывал вам компилятор, демонстрирует, что компилятор на самом деле не соответствует (если вы не скомпилировали в режиме C). Потому что, согласно предупреждению, он преобразовал нулевой указатель (не тот, nullptrкоторый был бы nullptr_t, который был бы отличен), так что, очевидно, определение NULLдействительно (void*)0, чего не может быть.

В любом случае, у вас есть два возможных законных (то есть, не нарушенных компилятором) случая. Либо (реалистичный случай), NULLэто что-то вроде 0или 0L, тогда у вас есть «ноль или один» преобразование в целое число, и вы готовы идти.

Или NULLэто действительно так nullptr. В этом случае у вас есть отдельное значение, которое имеет гарантии относительно сравнения, а также четко определенные преобразования из целых чисел, но, к несчастью, не в целые числа. Тем не менее, он имеет четко определенное преобразование в bool(в результате false) и boolимеет четко определенное преобразование в целое число (в результате 0).

К сожалению, это два преобразования, поэтому оно не находится в пределах «ноль или один», как указано в [conv]. Таким образом, если ваша реализация определяет NULLкак nullptr, то вам придется добавить явное приведение, чтобы ваш код был правильным.

Damon
источник
6

Из C faq:

Q: Если NULL и 0 эквивалентны как константы нулевого указателя, что я должен использовать?

A: Это только в контекстах указателя, NULLи 0они эквивалентны. NULLследует не использоваться , когда требуется другой вид 0, хотя это могло бы работать, так как это посылает неверное стилистическое сообщение. (Кроме того, ANSI допускает определение NULL ((void *)0), которое не будет работать вообще в контекстах без указателей.) В частности, не используйте, NULLкогда требуется нулевой символ ASCII (NUL). Укажите свое собственное определение

http://c-faq.com/null/nullor0.html

phuclv
источник
5

Отказ от ответственности: я не знаю C ++. Мой ответ не предназначен для применения в контексте C ++

'\0'это intсо значением ноль, просто на 100% точно так же 0.

for (int k = 10; k > '\0'; k--) /* void */;
for (int k = 10; k > 0; k--) /* void */;

В контексте указателей , 0и NULLявляются 100% эквивалентны:

if (ptr) /* ... */;
if (ptr != NULL) /* ... */;
if (ptr != '\0') /* ... */;
if (ptr != 0) /* ... */;

все 100% эквивалентны.


Примечание о ptr + NULL

Контекст неptr + NULL является указателем. Нет определения для добавления указателей в языке Си; указатели и целые числа могут быть добавлены (или вычтены). В случае либо или является указателем, а другой должен быть целым числом, так эффективно , или и в зависимости от определений и несколько поведений можно ожидать: все это работает, предупреждение для преобразования между указателем и целое, отказ компилировать, .. ,ptr + NULLptrNULLptr + NULL(int)ptr + NULLptr + (int)NULLptrNULL

PMG
источник
Я видел #define NULL (void *)0раньше. Вы уверены, что NULL и обычный 0 равны 100%?
machine_1
2
В контексте указателя да ... условие подчеркнуто в моем ответе, спасибо
pmg
@phuclv: я понятия не имею о C ++. Мой ответ (кроме бита между скобками) о C
pmg
@phuclv ptr + NULLне используется NULLв контексте указателей
pmg
3
@JesperJuhl: в контексте указателей они на 100% эквивалентны. Я понятия не имею, что nullptrесть, но ((void*)0)и 0(или '\0') эквивалентны в контексте указателей ...if (ptr == '\0' /* or equivalent 0, NULL */)
pmg
5

Нет, больше не используется NULL(старый способ инициализации указателя).

Начиная с C ++ 11:

Ключевое слово nullptrобозначает литерал указателя. Это значение типа std :: nullptr_t. Существуют неявные преобразования из nullptrнулевого значения указателя любого типа указателя и любого указателя на тип члена. Подобные преобразования существуют для любой константы нулевого указателя, которая включает значения типа, std::nullptr_tа также макрос NULL.

https://en.cppreference.com/w/cpp/language/nullptr

На самом деле, станд :: nullptr_t типа нулевого указателя буквального nullptr. Это отдельный тип, который сам по себе не является типом указателя или указателем на тип члена.

#include <cstddef>
#include <iostream>

void f(int* pi)
{
   std::cout << "Pointer to integer overload\n";
}

void f(double* pd)
{
   std::cout << "Pointer to double overload\n";
}

void f(std::nullptr_t nullp)
{
   std::cout << "null pointer overload\n";
}

int main()
{
    int* pi; double* pd;

    f(pi);
    f(pd);
    f(nullptr);  // would be ambiguous without void f(nullptr_t)
    // f(0);  // ambiguous call: all three functions are candidates
    // f(NULL); // ambiguous if NULL is an integral null pointer constant 
                // (as is the case in most implementations)
}

Вывод:

Pointer to integer overload
Pointer to double overload
null pointer overload
Mannoj
источник
Вопрос в том, чтобы присвоить NULL 0 для целых чисел. В этом смысле ничего не меняется с nullptr вместо NULL.
ivan.ukr
Он использовал слова как «NULL указатель».
Манной
Кстати, в C ++ нет концепции NULL после C ++ 11. Автор может запутаться в использовании constexpr или определить старый способ инициализации. ru.cppreference.com/w/cpp/language/default_initialization
Манной