Подтвердить что ты не робот

Может '\ 0' и NULL использоваться взаимозаменяемо?

NULL часто используется в контексте указателей и определяется с помощью макросов в нескольких стандартных библиотеках (например, <iostream>) как целое число 0. '\0' является нулевым символом и составляет 8 бит нулей. Кстати, 8 бит нулей эквивалентно целому числу 0.

В некоторых случаях, хотя считается, что это ужасный стиль, эти два могут быть взаимозаменяемы:

int *p='\0';
if (p==NULL) //evaluates to true
    cout << "equal\n";

или

char a=NULL;
char b='\0';
if (a==b) //evaluates to true
    cout << "equal again\n";

Уже существует много подобных вопросов по SO только; например, верхний ответ на этот вопрос (В чем разница между NULL, '\ 0' и 0) говорит: "На самом деле это не то же самое".

Может ли кто-нибудь представить пример, что NULL и \0 не могут быть взаимозаменяемы (предпочтительно фактическое приложение, а не патологический случай)?

4b9b3361

Ответ 1

Может ли кто-нибудь представить пример, что NULL и \0 не могут быть заменены?

Разница между NULL и '\0' может повлиять на разрешение перегрузки.

Пример (проверить его на Coliru):

#include <iostream>

// The overloaded function under question can be a constructor or 
// an overloaded operator, which would make this example less silly
void foo(char)   { std::cout << "foo(char)"  << std::endl; }
void foo(int)    { std::cout << "foo(int)"   << std::endl; }
void foo(long)   { std::cout << "foo(long)"  << std::endl; }
void foo(void*)  { std::cout << "foo(void*)" << std::endl; }

int main()
{
    foo('\0'); // this will definitely call foo(char)
    foo(NULL); // this, most probably, will not call foo(char)
}

Обратите внимание, что компилятор gcc, используемый в Coliru, определяет NULL как 0L, который для этого примера означает, что foo(NULL) разрешается foo(long), а не foo(void*). Ответ Ville-Valtteri Tiittanen подробно обсуждает этот аспект.

Ответ 2

Определение макроса NULL в С++

Леон прав., что при наличии нескольких перегрузок для одной и той же функции \0 предпочтет тот, который принимает параметр типа char. Тем не менее, важно заметить, что в типичном компиляторе NULL предпочтет перегрузку, которая принимает параметр типа int, а не типа void*!

Что, вероятно, вызывает эту путаницу, так это то, что язык C позволяет определять NULL как (void*)0. В стандарте С++ явно указано (черновик N3936, стр. 444):

Возможные определения [из макроса NULL] включают 0 и 0L, но не (void*)0.

Это ограничение необходимо, так как, например, char *p = (void*)0 является допустимым C, но недействительным С++, тогда как char *p = 0 действует в обоих случаях.

В С++ 11 и более поздних версиях вы должны использовать nullptr, если вам нужна нулевая константа, которая ведет себя как указатель.

Как действует предложение Леона на практике

Этот код определяет несколько перегрузок одной функции. Каждая перегрузка выводит тип параметра:

#include <iostream>

void f(int) {
    std::cout << "int" << std::endl;
}

void f(long) {
    std::cout << "long" << std::endl;
}

void f(char) {
    std::cout << "char" << std::endl;
}

void f(void*) {
    std::cout << "void*" << std::endl;
}

int main() {
    f(0);
    f(NULL);
    f('\0');
    f(nullptr);
}

В Ideone это выводит

int
int
char
void*

Поэтому я бы сказал, что проблема с перегрузками - это не фактическое приложение, а патологический случай. Константа NULL будет вести себя неправильно и должна быть заменена на nullptr в С++ 11.

Что делать, если NULL не равен нулю?

Другой патологический случай предложен Эндрю Китоном по другому вопросу:

Обратите внимание, что это нулевой указатель на языке C. Это не имеет значения для базовой архитектуры. Если базовая архитектура имеет значение нулевого указателя, определяемое как адрес 0xDEADBEEF, то компилятор должен сортировать этот беспорядок.

Таким образом, даже в этой смешной архитектуре все еще существуют способы проверки нулевого указателя:

if (!pointer)
if (pointer == NULL)
if (pointer == 0)

Ниже перечислены НЕВЕРНЫЕ способы проверки нулевого указателя:

#define MYNULL (void *) 0xDEADBEEF
if (pointer == MYNULL)
if (pointer == 0xDEADBEEF)

поскольку они рассматриваются компилятором как обычные сравнения.

Резюме

В целом, я бы сказал, что различия в основном стилистичны. Если у вас есть функция, которая принимает int и перегружает, что принимает char, и они работают по-разному, вы заметите разницу, когда вы вызываете их с константами \0 и NULL. Но как только вы помещаете эти константы в переменные, разница исчезает, потому что вызываемая функция вычитается из типа переменной.

Использование правильных констант делает код более ремонтопригодным и передает значение лучше. Вы должны использовать 0, когда вы имеете в виду число, \0, когда вы имеете в виду символ, и nullptr, когда вы имеете в виду указатель. Matthieu M. указывает в комментариях, что У GCC была ошибка, в которой a char* сравнивали с \0, тогда как целью было разыменовать указатель и сравнить a char с \0. Такие ошибки легче обнаружить, если правильный стиль используется основательно для кодовой базы.

Чтобы ответить на ваш вопрос, на самом деле не существует фактического варианта использования, который бы мешал вам использовать \0 и NULL взаимозаменяемо. Просто стилистические причины и некоторые крайние случаи.

Ответ 3

Пожалуйста, не делайте этого. Это анти-шаблон, и на самом деле это неправильно. NULL для NULL-указателей, '\0' - нулевой символ. Они логически разные вещи.

Я не думаю, что когда-либо видел это:

int* pVal='\0';

Но это довольно часто:

char a=NULL;

Но это не хорошая форма. Это делает код менее портативным и, на мой взгляд, менее читаемым. Вероятно, это также вызовет проблемы в смешанных средах C/С++.

Он полагается на предположения относительно того, как любая конкретная реализация определяет NULL. Например, в некоторых реализациях используется простая

#define NULL 0

Другие могут использовать:

#define NULL ((void*) 0)

И я видел, как другие определяли как целое число, и все виды нечетного лечения.

NULL должен, на мой взгляд, использоваться исключительно для указания недействительного адреса. Если вам нужен нулевой символ, используйте '\0'. Или определите это как NULLCHR. Но это не так чисто.

Это сделает ваш код более переносимым - вы не начнете получать предупреждения о типах и т.д., если вы измените настройки компилятора/среды/компилятора. Это может быть более важным в C или смешанной среде C/С++.

Пример предупреждений, которые могут возникнуть: Рассмотрим этот код:

#define NULL 0
char str[8];
str[0]=NULL;

Это эквивалентно:

#define NULL 0
char str[8];
str[0]=0;

И мы присваиваем целочисленное значение char. Это может вызвать предупреждение компилятора, и если этого достаточно, довольно скоро вы не увидите никаких важных предупреждений. И для меня это настоящая проблема. Наличие предупреждений в коде имеет два побочных эффекта:

  • Учитывая достаточно предупреждений, вы не видите новых.
  • Он дает сигнал о том, что предупреждения приемлемы.

В обоих случаях фактические ошибки могут проскальзывать, которые были бы пойманы компилятором, если бы мы потрудились прочитать предупреждения (или включить -Werror)

Ответ 4

Да, они могут проявлять различное поведение при выполнении перегруженных функций.

func('\0') вызывает func(char),

while

func(NULL) вызывает func(integer_type).


Вы можете устранить путаницу, используя nullptr, который всегда является типом указателя, не отображает двусмысленности при назначении/сравнении значения или перегрузке функции разрешающая способность.

char a = nullptr; //error : cannot convert 'std::nullptr_t' to 'char' in initialization
int x = nullptr;  //error : nullptr is a pointer not an integer

Обратите внимание, что он по-прежнему совместим с NULL:

int *p=nullptr;
if (p==NULL) //evaluates to true

Выдержка из С++ Programming Книга 4-го издания Страуструпа:

В более старом коде вместо 0ptpt обычно используется 0 или NULL (§7.2.2). Однако использование nullptr устраняет потенциальную путаницу между целыми числами (например, 0 или NULL) и указателями (такими как nullptr).


Ответ 5

Компьютерные программы имеют два типа считывателей.

Первый тип - это компьютерные программы, такие как компилятор.

Второй тип - люди, как вы и ваши коллеги.

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

Важно то, что вы возитесь с человеческими читателями.

Человеческие читатели очень чувствительны к контексту. Используя нулевой нуль, вы лежа для вас, читателей. Они будут проклинать вас.

Человек, которому доверяют, может легче пропустить ошибки.
Человек, которому лгут, может видеть "ошибки", которых нет. Когда "исправляя" эти фантомные ошибки, они вводят реальные ошибки.

Не лгите своим людям. Один из людей, которым вы лжете, - это ваше будущее. Вы тоже будете проклинать вас.

Ответ 6

Выдержки из проекта С++ 14 N3936:

18.2 Типы [support.types]

3 Макрос NULL является константой указателя нулевой указатель С++, указанной в этом Международном стандарте (4.10).

4.10 Преобразования указателя [conv.ptr]

1 Константа нулевого указателя представляет собой целочисленный литерал (2.14.2) со значением 0 или значением типа std::nullptr_t.
Константа нулевого указателя может быть преобразована в тип указателя; результатом является нулевое значение указателя этого типа и отличается от любого другого значения указателя объекта или типа указателя функции.

Таким образом, NULL может быть любым целым литералом со значением 0 или значением типа std::nullptr_t, как nullptr, тогда как '\0' всегда является нулевым символом узкого символа.

Таким образом, не в общем взаимозаменяемом, хотя в контексте-указателе вы не видите никакой стилистической разницы.

Пример:

#include <iostream>
#include <typeinfo>

int main() {
    std::cout << typeid('\0').name << '\n'
        << typeid(NULL).name << '\n'
        << typeid(nullptr).name << '\n';
}

Ответ 7

Определить, что NULL в C/С++.

В соответствии с C/С++ ссылкой NULL определяется как макрос, который расширяется до нулевой константы указателя. Далее мы можем прочитать, что константа нулевого указателя может быть преобразована в любой тип указателя (или тип-указатель-член), который получает значение нулевого указателя. Это специальное значение, указывающее, что указатель не указывает на какой-либо объект.

Определение, относящееся к C:

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

Определение, относящееся к С++ 98:

Константа нулевого указателя является интегральным постоянным выражением, которое вычисляется до нуля (например, 0 или 0L).

Определение, относящееся к С++ 11:

Константа нулевого указателя является либо интегральным константным выражением, которое вычисляется до нуля (например, 0 или 0L), либо значением типа nullptr_t (например, nullptr).

Пример методов перегрузки.

Предположим, что мы имеем следующие методы:

class Test {
public:
    method1(char arg0);
    method1(int arg0);
    method1(void* arg0);
    method1(bool arg0);
}

Вызов метода1 с аргументом NULL или nullptr должен вызвать method1(void* arg0);. Однако, если мы вызываем метод1 с аргументом '\0' или 0, следует выполнить method1(char arg0); и method1(int arg0);.