Ответ зависит от вашей точки зрения:
Если вы судите по стандарту 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:
Нулевые ссылки - это нечто ужасное:
Их существование кажется невозможным (= по стандарту),
но они существуют (= сгенерированным машинным кодом),
но вы не можете их видеть, если они существуют (= ваши попытки будут оптимизированы),
но они в любом случае могут убить вас, не подозревая (= ваша программа вылетает из-за странных моментов или того хуже).
Ваша единственная надежда в том, что их не существует (= напишите свою программу, чтобы не создавать их).
Я очень надеюсь, что это не станет вас преследовать!