Ответ зависит от вашей точки зрения:
Если вы судите по стандарту 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
член (ответ : экземпляр в вызове члена был пустой ссылкой, поэтому this
является нулевым указателем, и ваш член вычисляется как адрес 8).
Так как насчет проверки на нулевые ссылки? Вы дали строку
if( & nullReference == 0 ) // null reference
в вашем вопросе. Что ж, это не сработает: в соответствии со стандартом, у вас неопределенное поведение, если вы разыменовываете нулевой указатель, и вы не можете создать нулевую ссылку, не разыменовав нулевой указатель, поэтому нулевые ссылки существуют только внутри области неопределенного поведения. Поскольку ваш компилятор может предполагать, что вы не запускаете неопределенное поведение, он может предполагать, что такой вещи, как нулевая ссылка, не существует (даже если он с готовностью генерирует код, генерирующий нулевые ссылки!). Таким образом, он видит условие if()
, делает вывод, что оно не может быть истинным, и просто отбрасывает весь оператор if()
. С введением оптимизации времени ссылки стало невозможным надежно проверять нулевые ссылки.
TL; DR:
Нулевые ссылки являются чем-то ужасным:
Их существование кажется невозможным (= по стандарту),
но они существуют (= по сгенерированному машинному коду),
но вы не можете видеть их, если они существуют (= ваши попытки будут оптимизированы),
но в любом случае они могут убить вас, даже не подозревая (= ваша программа падает в странных точках или еще хуже).
Ваша единственная надежда состоит в том, что они не существуют (= напишите вашу программу, чтобы не создавать их).
Надеюсь, это вас не преследует!