Возможна ли нулевая ссылка?


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

int &nullReference = *(int*)0;

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

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

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

4 ответа:

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

8.3.2/1:

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

1.9/4:

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

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

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

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

Edit: Исправлено несколько опечаток и добавлен оператор if-в main () для проверки того, что оператор cast-to-pointer действительно работает (что я забыл.. мой плохо) - 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;
  }
}

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.

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


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


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

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

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

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

#include "converter.h"

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

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

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


так как насчет проверки на null ссылки? Вы дали линию

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

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


TL; DR:

нулевые ссылки несколько ужасны существование:

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

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