La respuesta depende de tu punto de vista:
Si juzga por el estándar C ++, no puede obtener una referencia nula porque primero obtiene un comportamiento indefinido. Después de esa primera incidencia de comportamiento indefinido, el estándar permite que suceda cualquier cosa. Entonces, si escribe *(int*)0
, ya tiene un comportamiento indefinido como lo es, desde el punto de vista estándar del lenguaje, desreferenciar un puntero nulo. El resto del programa es irrelevante, una vez que se ejecuta esta expresión, estás fuera del juego.
Sin embargo, en la práctica, las referencias nulas se pueden crear fácilmente a partir de punteros nulos y no se dará cuenta hasta que intente acceder al valor detrás de la referencia nula. Su ejemplo puede ser demasiado simple, ya que cualquier buen compilador de optimización verá el comportamiento indefinido y simplemente optimizará todo lo que dependa de él (la referencia nula ni siquiera se creará, se optimizará).
Sin embargo, la optimización depende de que el compilador demuestre el comportamiento indefinido, lo que puede no ser posible. Considere esta función simple dentro de un archivo converter.cpp
:
int& toReference(int* pointer) {
return *pointer;
}
Cuando el compilador ve esta función, no sabe si el puntero es un puntero nulo o no. Entonces solo genera código que convierte cualquier puntero en la referencia correspondiente. (Por cierto: esto es un error ya que los punteros y las referencias son exactamente la misma bestia en ensamblador). Ahora, si tiene otro archivo user.cpp
con el código
#include "converter.h"
void foo() {
int& nullRef = toReference(nullptr);
cout << nullRef; //crash happens here
}
el compilador no sabe que toReference()
eliminará la referencia del puntero pasado y asumirá que devuelve una referencia válida, que resultará ser una referencia nula en la práctica. La llamada se realiza correctamente, pero cuando intenta utilizar la referencia, el programa se bloquea. Ojalá. El estándar permite que suceda cualquier cosa, incluida la aparición de elefantes rosados.
Puede preguntarse por qué esto es relevante, después de todo, el comportamiento indefinido ya se desencadenó en el interior toReference()
. La respuesta es depurar: las referencias nulas pueden propagarse y proliferar al igual que lo hacen los punteros nulos. Si no sabe que pueden existir referencias nulas y aprende a evitar crearlas, puede pasar bastante tiempo tratando de averiguar por qué su función de miembro parece fallar cuando solo intenta leer un int
miembro antiguo simple (respuesta: la instancia en la llamada del miembro era una referencia nula, por lo que this
es un puntero nulo, y su miembro se calcula para ubicarse como dirección 8).
Entonces, ¿qué tal si verificamos referencias nulas? Le diste la linea
if( & nullReference == 0 ) // null reference
en tu pregunta. Bueno, eso no funcionará: de acuerdo con el estándar, tiene un comportamiento indefinido si desreferencia un puntero nulo, y no puede crear una referencia nula sin desreferenciar un puntero nulo, por lo que las referencias nulas existen solo dentro del ámbito del comportamiento indefinido. Dado que su compilador puede asumir que no está activando un comportamiento indefinido, puede asumir que no existe una referencia nula (¡aunque emitirá fácilmente código que genera referencias nulas!). Como tal, ve la if()
condición, concluye que no puede ser verdadera y simplemente desecha toda la if()
afirmación. Con la introducción de optimizaciones de tiempo de enlace, se ha vuelto simplemente imposible verificar referencias nulas de una manera sólida.
TL; DR:
Las referencias nulas tienen una existencia algo espantosa:
Su existencia parece imposible (= según el estándar),
pero existen (= según el código de máquina generado),
pero no puede verlos si existen (= sus intentos se optimizarán),
pero de todos modos pueden matarlo sin darse cuenta (= su programa se bloquea en puntos extraños o peores).
Tu única esperanza es que no existan (= escribe tu programa para no crearlos).
¡Espero que eso no te persiga!