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

Могу ли я использовать указатель 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 в качестве значения для работы с присваиваниями и другие арифметические операции.


person RobertS supports Monica Cellio    schedule 21.12.2019    source источник
comment
Комментарии не подлежат расширенному обсуждению; этот разговор был перешел в чат.   -  person Samuel Liew♦    schedule 24.12.2019
comment
Было бы лучше задать два отдельных вопроса: один для C, а другой - для C ++.   -  person Konrad Rudolph    schedule 24.12.2019


Ответы (7)


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

person John Bollinger    schedule 21.12.2019
comment
Вы должны указать, что тип, который обычно имеет тип void *, истинен только для C. void * не является допустимым типом для C ++ (потому что вы не можете назначить void* любому другому типу указателя). В C ++ 89 и C ++ 03 фактически NULL должен иметь тип int, но в более поздних версиях он может быть (и обычно так) nullptr_t. - person Martin Bonner supports Monica; 23.12.2019
comment
Вы также ошибаетесь, что преобразование void* в int является неопределенным поведением. Нет; это реализация указанного поведения. - person Martin Bonner supports Monica; 23.12.2019
comment
@MartinBonnersupportsMonica, в тех контекстах, где C указывает, что указатель преобразуется в целое число, результат преобразования действительно определяется реализацией, но я говорю не об этом. Это присвоение указателя lvalue целочисленного типа (без явного преобразования с помощью приведения), которое имеет неопределенное поведение. Там язык не определяет автоматическую конвертацию. - person John Bollinger; 23.12.2019
comment
@MartinBonnersupportsMonica, я отредактировал, чтобы больше учитывать соображения C ++. В любом случае центральная тема одинаково применима к обоим языкам: если вам нужно целое число 0, то запишите его явно как целочисленную константу соответствующего типа. - person John Bollinger; 23.12.2019

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 в целое число нет. Вот почему она считается более безопасной константой с нулевым указателем.

person StoryTeller - Unslander Monica    schedule 21.12.2019
comment
Для полноты, 0L также является константой нулевого указателя и может использоваться как NULL на обоих языках. - person eerorika; 21.12.2019
comment
@eerorika - Думаю, мы можем сделать еще лучше. Скопировал полное определение в ответ. - person StoryTeller - Unslander Monica; 21.12.2019
comment
@eerorika: ВСЕГДА является ли 0L нулевым указателем? Я вспоминаю сегментированные архитектуры, такие как 80286, где дальний указатель был двумя отдельными регистрами, а в IIRC вы могли корректно установить смещение на ноль. (Не говоря уже о том, что IMHO всегда лучше сказать именно то, что вы имеете в виду в коде. Если вы присваиваете числовую переменную, используйте 0 или 0,0; если для указателя, то NULL.) - person jamesqf; 22.12.2019
comment
@jamesqf Стандарт говорит, что целочисленная константа со значением 0 является константой нулевого указателя. Следовательно, 0L - это константа нулевого указателя. - person eerorika; 22.12.2019
comment
@eerorika: Именно то, что нужно миру, стандарты, игнорирующие реальность :-) Потому что, если я правильно помню свою сборку 80286, вы даже не можете назначить дальний указатель как отдельную операцию, поэтому разработчикам компилятора пришлось бы специально -Буди это. - person jamesqf; 23.12.2019
comment
@jamesqf Согласно C FAQ, re: создание 0 константы нулевого указателя: очевидно, как подачка ко всему существующему плохо написанному коду C, который делал неверные предположения - person Andrew Henle; 23.12.2019
comment
@jamesqf, что любая целочисленная константа со значением 0 является константой с нулевым указателем (в C) не имеет ничего общего с реализациями аппаратных указателей. Также обратите внимание, что стандартный C не распознает различия между ближними и дальними указателями в любом случае, но поддерживает присвоение указателей указателям. Он также поддерживает (некоторые) сравнения указателей, которые представляют интересные проблемы для форматов сегментированной адресации, таких как 286. - person John Bollinger; 23.12.2019
comment
@jamesqf: он игнорирует реальность неважным образом. Обратите внимание, что гарантированно работают только ICE со значением 0, а не просто любое старое целочисленное выражение, которое имеет значение 0. Так что да, компилятор может использовать его в особом случае, если это необходимо, и заменять его любым фактическим нулевым указателем - шаблон, потому что во время компиляции известно, является ли целочисленное выражение ICE или нет. - person Steve Jessop; 24.12.2019
comment
Тогда void *foo = 0L; не обязательно будет иметь тот же результат, что и long get_null() { return 0;} ... void *foo = get_null();. Я не верю, что это создает проблемы для разработчиков компиляторов, только для заблуждающегося автора get_null. Фактически, стандарт гласит, что присвоение ICE значения 0 указателю имеет особое значение, которое заключается в установке для указателя значения NULL. Как только вы знаете представление нулевого указателя, это несложно реализовать в любой архитектуре :-) - person Steve Jessop; 24.12.2019
comment
Или, другими словами, обработка 0-значных ICE как особого случая до C ++ 11 считалась лучше, чем наличие типа с нулевым указателем. Я подозреваю (но не уверен), что это связано с тем, что на самом деле разработчикам-компиляторам считалось проще реализовать особый случай, чем особый тип! - person Steve Jessop; 24.12.2019

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

int i = NULL;

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

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

[lib.support.types]

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

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

[conv.ptr]

Константа нулевого указателя представляет собой целочисленное постоянное выражение (5.19) rvalue целочисленного типа, значение которого равно нулю.

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

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

[conv.ptr]

Константа нулевого указателя - это целочисленный литерал ([lex.icon]) с нулевым значением или prvalue типа std :: nullptr_t.

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

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


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

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

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

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

person eerorika    schedule 21.12.2019

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

Хотя это действительно, действительно, очень плохой стиль (что и говорить?).

NULL является или был на самом деле не C ++, это C. Стандарт имеет, однако, как и для многих унаследованных C, имеет два предложения ([diff.null] и [ support.types.nullptr]), которые технически составляют NULL C ++. Это константа нулевого указателя, определяемая реализацией. Поэтому, даже если это плохой стиль, технически он такой же, как 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, вам нужно будет добавить явное приведение, чтобы ваш код был правильным.

person Damon    schedule 22.12.2019

Из C часто задаваемых вопросов:

В: Что мне следует использовать, если NULL и 0 эквивалентны константам нулевого указателя?

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

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

person phuclv    schedule 21.12.2019

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

person pmg    schedule 21.12.2019
comment
Я видел #define NULL (void *)0 раньше. Вы уверены, что NULL и простой 0 на 100% эквивалентны? - person machine_1; 21.12.2019
comment
В контексте указателя, да ... условие, подчеркнутое в моем ответе, спасибо - person pmg; 21.12.2019
comment
@phuclv: Я понятия не имею о C ++. Мой ответ (кроме бита в скобках) о C - person pmg; 21.12.2019
comment
@phuclv ptr + NULL не использует NULL в контексте указателей - person pmg; 21.12.2019
comment
0 и NULL на 100% эквивалентны - нет. NULL часто (но не всегда) определяется как ((void*)0), что не то же самое, что 0. И уж точно не то же самое, что nullptr. - person Jesper Juhl; 21.12.2019
comment
@JesperJuhl: в контексте указателей они на 100% эквивалентны. Я понятия не имею, что такое nullptr, но ((void*)0) и 0 (или '\0') эквивалентны в контексте указателей ... if (ptr == '\0' /* or equivalent 0, NULL */) - person pmg; 21.12.2019

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

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

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

http://Reference%20for%20documetation

Фактически, std :: 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
person Build Succeeded    schedule 24.12.2019
comment
Вопрос в том, чтобы присвоить NULL значение 0 для целых чисел. В этом смысле ничего не меняется с nullptr вместо NULL. - person ivan.ukr; 25.12.2019
comment
Кстати, в C ++ нет концепции NULL после C ++ 11. Автор может быть сбит с толку использованием constexpr или определения старого способа инициализации. en.cppreference.com/w/cpp/language/default_initialization - person Build Succeeded; 25.12.2019