¿Es este código válido (y comportamiento definido)?
int &nullReference = *(int*)0;
Tanto g ++ y sonido metálico ++ compilación sin ninguna advertencia, incluso cuando se utilizan -Wall
, -Wextra
, -std=c++98
, -pedantic
, -Weffc++
...
Por supuesto, la referencia no es realmente nula, ya que no se puede acceder a ella (significaría desreferenciar un puntero nulo), pero podríamos comprobar si es nula o no comprobando su dirección:
if( & nullReference == 0 ) // null reference
c++
reference
null
language-lawyer
peoro
fuente
fuente
if (false)
, eliminando el cheque, precisamente porque las referencias no pueden ser nulas de todos modos. Existía una versión mejor documentada en el kernel de Linux, donde se optimizó una verificación NULL muy similar: isc.sans.edu/diary.html?storyid=6820Respuestas:
Las referencias no son indicadores.
8.3.2 / 1:
1,9 / 4:
Como dice Johannes en una respuesta eliminada, hay algunas dudas sobre si "desreferenciar un puntero nulo" debe declararse categóricamente como un comportamiento indefinido. Pero este no es uno de los casos que generan dudas, ya que un puntero nulo ciertamente no apunta a un "objeto o función válida", y no hay ningún deseo dentro del comité de estándares de introducir referencias nulas.
fuente
&*p
comop
universalmente, eso no descarta un comportamiento indefinido (que por su naturaleza puede "parecer funcionar"); y no estoy de acuerdo con que unatypeid
expresión que busca determinar el tipo de un "puntero nulo desreferenciado" en realidad desreferencia al puntero nulo. He visto a gente discutir seriamente que&a[size_of_array]
no se puede ni se debe confiar en ellos y, de todos modos, es más fácil y seguro escribira + size_of_array
.*p
refiere lvalue , cuándop
es un puntero nulo. C ++ actualmente no tiene la noción de un lvalue vacío, que el problema 232 quería introducir.typeid
trabajos basados en sintaxis, en lugar de basados en semántica. Es decir, si lo hacetypeid(0, *(ostream*)0)
usted lo hace tener un comportamiento indefinido - nobad_typeid
se garantiza que sea lanzado, a pesar de que se pasa un valor izquierdo como resultado de una referencia de puntero nulo semánticamente. Pero sintácticamente en el nivel superior, no es una desreferencia, sino una expresión de operador de coma.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
: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ódigoel 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 unint
miembro antiguo simple (respuesta: la instancia en la llamada del miembro era una referencia nula, por lo quethis
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
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 laif()
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!
fuente
Si su intención era encontrar una manera de representar nulo en una enumeración de objetos singleton, entonces es una mala idea (des) hacer referencia a nulo (es C ++ 11, nullptr).
¿Por qué no declarar un objeto singleton estático que representa NULL dentro de la clase de la siguiente manera y agregar un operador de conversión a puntero que devuelve nullptr?
Editar: se corrigieron varios tipos incorrectos y se agregó una declaración if en main () para probar si el operador de conversión a puntero funcionaba realmente (que olvidé ... mi mal) - 10 de marzo de 2015 -
fuente
clang ++ 3.5 incluso advierte sobre ello:
fuente