Возможна ли пустая ссылка?

Является ли этот код кода действительным (и определенным поведением)?

int &nullReference = *(int*)0;

Оба g++ и clang++ компилируют его без предупреждения даже при использовании -Wall, -Wextra, -std=c++98, -pedantic, -Weffc++...

Конечно, ссылка не является фактически нулевой, так как она не может быть доступна (это означало бы разыменование нулевого указателя), но мы могли бы проверить, является ли она нулевой или нет, проверив ее адрес:

if( & nullReference == 0 ) // null reference

Ответ 1

Ссылки не являются указателями.

8.3.2/1:

Ссылка должна быть инициализирована обратитесь к действительному объекту или функции. [Примечание: в частности, нулевая ссылка не может существовать в четко определенном программы, потому что единственный способ создать такую ​​ссылку было бы привязать его к "объекту", полученному разыменование нулевого указателя, который вызывает поведение undefined. В виде описанный в 9.6, ссылка не может быть привязаны непосредственно к битовому полю. ]

1,9/4:

Некоторые другие операции описаны в этом Международном стандарте undefined (например, эффект разыменование нулевого указателя)

Как говорит Йоханнес в удаленном ответе, есть некоторые сомнения, следует ли категорически заявлять о "разыменовании нулевого указателя" как о undefined. Но это не один из случаев, вызывающих сомнения, поскольку нулевой указатель, конечно, не указывает на "действительный объект или функцию", и в комитете по стандартизации нет желания вводить нулевые ссылки.

Ответ 2

Если бы вы намеревались найти способ представления null в перечислении одноэлементных объектов, тогда это плохая идея для (de) ссылки null (это С++ 11, nullptr).

Почему бы не объявить статический одноэлементный объект, представляющий NULL внутри класса, следующим образом и добавить оператор cast-to-pointer, который возвращает nullptr?

Изменить: Исправлено несколько ошибок и добавлено if-statement в main(), чтобы проверить, работает ли оператор-оператор-указатель (который я забыл.. мой плохой) - 10 марта 2015 г. -

// Error.h
class Error {
public:
  static Error& NOT_FOUND;
  static Error& UNKNOWN;
  static Error& NONE; // singleton object that represents null

public:
  static vector<shared_ptr<Error>> _instances;
  static Error& NewInstance(const string& name, bool isNull = false);

private:
  bool _isNull;
  Error(const string& name, bool isNull = false) : _name(name), _isNull(isNull) {};
  Error() {};
  Error(const Error& src) {};
  Error& operator=(const Error& src) {};

public:
  operator Error*() { return _isNull ? nullptr : this; }
};

// Error.cpp
vector<shared_ptr<Error>> Error::_instances;
Error& Error::NewInstance(const string& name, bool isNull = false)
{
  shared_ptr<Error> pNewInst(new Error(name, isNull)).
  Error::_instances.push_back(pNewInst);
  return *pNewInst.get();
}

Error& Error::NOT_FOUND = Error::NewInstance("NOT_FOUND");
//Error& Error::NOT_FOUND = Error::NewInstance("UNKNOWN"); Edit: fixed
//Error& Error::NOT_FOUND = Error::NewInstance("NONE", true); Edit: fixed
Error& Error::UNKNOWN = Error::NewInstance("UNKNOWN");
Error& Error::NONE = Error::NewInstance("NONE");

// Main.cpp
#include "Error.h"

Error& getError() {
  return Error::UNKNOWN;
}

// Edit: To see the overload of "Error*()" in Error.h actually working
Error& getErrorNone() {
  return Error::NONE;
}

int main(void) {
  if(getError() != Error::NONE) {
    return EXIT_FAILURE;
  }

  // Edit: To see the overload of "Error*()" in Error.h actually working
  if(getErrorNone() != nullptr) {
    return EXIT_FAILURE;
  }
}

Ответ 3

clang++ 3.5 даже предупреждает об этом:

/tmp/a.C:3:7: warning: reference cannot be bound to dereferenced null pointer in well-defined C++ code; comparison may be assumed to
      always evaluate to false [-Wtautological-undefined-compare]
if( & nullReference == 0 ) // null reference
      ^~~~~~~~~~~~~    ~
1 warning generated.

Ответ 4

Ответ зависит от вашей точки зрения:


Если вы судите по стандарту С++, вы не можете получить нулевую ссылку, потому что сначала получите поведение undefined. После этого первого падения поведения undefined, стандарт позволяет что-либо случиться. Итак, если вы пишете *(int*)0, у вас уже есть поведение undefined, как и вы, с точки зрения языка, разыменовывая нулевой указатель. Остальная часть программы не имеет значения, как только это выражение выполняется, вы вышли из игры.


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

Тем не менее, эта оптимизация зависит от компилятора, чтобы доказать поведение undefined, что может быть невозможно. Рассмотрим эту простую функцию внутри файла converter.cpp:

int& toReference(int* pointer) {
    return *pointer;
}

Когда компилятор видит эту функцию, он не знает, является ли указатель нулевым указателем или нет. Таким образом, он просто генерирует код, который превращает любой указатель в соответствующую ссылку. (Btw: Это noop, поскольку указатели и ссылки являются тем же самым зверем в ассемблере.) Теперь, если у вас есть другой файл user.cpp с кодом

#include "converter.h"

void foo() {
    int& nullRef = toReference(nullptr);
    cout << nullRef;    //crash happens here
}

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

Вы можете спросить, почему это актуально, ведь поведение undefined уже было запущено внутри toReference(). Ответ - отладка: нулевые ссылки могут распространяться и распространяться так же, как и нулевые указатели. Если вы не знаете, что могут существовать нулевые ссылки, и учитесь избегать их создания, вы можете потратить довольно много времени, пытаясь понять, почему ваша функция-член, кажется, падает, когда она просто пытается прочитать простого старого члена int (ответ: экземпляр в вызове элемента был пустой ссылкой, поэтому this является нулевым указателем, а ваш член вычисляется как адрес 8).


Итак, как насчет проверки нулевых ссылок? Вы дали строку

if( & nullReference == 0 ) // null reference

в вашем вопросе. Ну, это не сработает: согласно стандарту, у вас есть поведение undefined, если вы разыскиваете нулевой указатель, и вы не можете создать нулевую ссылку без разыменования нулевого указателя, поэтому нулевые ссылки существуют только внутри области undefined поведение. Поскольку ваш компилятор может предположить, что вы не запускаете поведение undefined, он может предположить, что нет такой вещи, как пустая ссылка (хотя она будет легко испускать код, который генерирует нулевые ссылки!). Таким образом, он видит условие if(), делает вывод, что он не может быть правдой и просто отбрасывает весь оператор if(). С введением оптимизации времени ссылки стало очевидным, что невозможно проверить нулевые ссылки надежным способом.


TL; DR:

Нулевые ссылки - это нечто ужасное:

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

Я надеюсь, что тебя не преследуют!