valores tipos salida retornan referencias referencia que punteros por paso parametros parametro operadores funciones entrada con c++ reference null language-lawyer

tipos - que es un parametro en c++



¿Es posible la referencia nula? (4)

¿Es esta pieza de código válida (y comportamiento definido)?

int &nullReference = *(int*)0;

Tanto g ++ como clang ++ compilan sin ninguna advertencia, incluso cuando se usa -Wall , -Wextra , -std=c++98 , -pedantic , -Weffc++ ...

Por supuesto, la referencia no es en realidad nula, ya que no se puede acceder (significaría desreferenciar un puntero nulo), pero podríamos verificar si es nula o no verificando su dirección:

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


La respuesta depende de tu punto de vista:

Si juzga por el estándar de 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 pase cualquier cosa. Por lo tanto, si escribe *(int*)0 , ya tiene un comportamiento indefinido tal como es, desde el punto de vista del idioma estándar, desreferenciando 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 pueden crearse fácilmente a partir de punteros nulos, y no se dará cuenta hasta que realmente intente acceder al valor detrás de la referencia nula. Su ejemplo puede ser un poco 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, esa optimización de distancia depende del compilador para probar el comportamiento indefinido, que puede no ser posible. Considere esta sencilla función dentro de un converter.cpp archivos.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. Por lo tanto, solo genera código que convierte cualquier puntero en la referencia correspondiente. (Por cierto: este es un noop ya que los punteros y las referencias son exactamente la misma bestia en el 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() desreferenciará el puntero pasado, y supondrá que devuelve una referencia válida, que pasará a ser una referencia nula en la práctica. La llamada tiene éxito, pero cuando intenta usar la referencia, el programa falla. Ojalá. El estándar permite que ocurra cualquier cosa, incluida la aparición de ping elefantes.

Puede preguntar por qué esto es relevante, después de todo, el comportamiento indefinido ya se desencadenó dentro de toReference() . La respuesta es la depuración: las referencias nulas pueden propagarse y proliferar como lo hacen los punteros nulos. Si no sabe que pueden existir referencias nulas, y aprende a evitar crearlas, puede pasar bastante tiempo tratando de descubrir por qué la función de miembro parece bloquearse cuando solo intenta leer un miembro simple antiguo int (respuesta: el 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é hay de buscar referencias nulas? Usted dio la línea

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

en tu pregunta. Bueno, eso no funcionará: según 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 solo existen dentro del ámbito de comportamiento indefinido. Dado que su compilador puede asumir que no está desencadenando un comportamiento indefinido, puede asumir que no existe una referencia nula (¡aunque emitirá código que genera referencias nulas!). Como tal, ve la condición if() , concluye que no puede ser verdad, y simplemente tira la declaración if() . Con la introducción de las optimizaciones del tiempo de enlace, se ha vuelto imposible verificar las referencias nulas de manera robusta.

TL; DR:

Las referencias nulas son algo así como una existencia espantosa:

Su existencia parece imposible (= por el estándar),
pero existen (= por el código máquina generado),
pero no puedes verlos si existen (= tus intentos serán optimizados),
pero pueden matarte inconscientemente de todos modos (= tu programa falla en puntos extraños, o peor).
Su única esperanza es que no existan (= escriba su programa para no crearlos).

¡Espero que eso no te atormente!


Las referencias no son punteros.

8.3.2 / 1:

Una referencia se inicializará para referirse a un objeto o función válida. [Nota: en particular, una referencia nula no puede existir en un programa bien definido, ya que la única forma de crear dicha referencia sería vincularla al "objeto" obtenido al desreferenciar un puntero nulo, lo que causa un comportamiento indefinido. Como se describe en 9.6, una referencia no puede vincularse directamente a un campo de bits. ]

1.9 / 4:

Algunas otras operaciones se describen en esta norma internacional como indefinidas (por ejemplo, el efecto de desreferenciar el puntero nulo)

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.


Si su intención era encontrar una forma de representar nulo en una enumeración de objetos singleton, entonces es una mala idea (de) referencia null (es C ++ 11, nullptr).

¿Por qué no declarar el objeto estático singleton que representa NULL dentro de la clase de la siguiente manera y agregar un operador de conversión a puntero que devuelve nullptr?

Editar: corrigió varios tipos erróneos y añadió una instrucción if en main () para probar el funcionamiento del operador de conversión de puntero a puntero (que olvidé ... mal) - 10 de marzo de 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 incluso lo advierte:

/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.