c++ - segmentacion - ¿Es posible tener una falla de segmentación de una referencia?
macrosegmentacion pdf (3)
Supongamos el siguiente código:
Foo & foo = getFoo();
foo.attr; // 100% safe access?
Si foo
fuera un puntero, verificaría si es NULL
, sin embargo, como es una referencia, tal comprobación no es necesaria. Lo que quiero saber es si es posible estropear la referencia de un objeto de modo que haga que el acceso a su atributo sea inseguro.
Intenté algunos ejemplos como intentar convertir NULL
a un objeto Foo
, pero obtuve errores de compilación. Solo quiero estar seguro de que el código anterior siempre es seguro, y de que no hay una posible magia negra interna en C++
que deba conocer.
A partir de la respuesta de Benjamin, podría crear un código de ejemplo en el que obtengo una falla de segmentación de una referencia, por lo que responde a mi pregunta. Pegaré mi código en caso de que alguien esté interesado en el futuro:
#include <iostream>
using namespace std;
class B
{
public:
int x;
B() {x = 5;}
};
class A
{
public:
void f()
{
b = *(B*)NULL;
}
B & getB()
{
return b;
}
B b;
};
int main()
{
A a;
a.f();
cout << a.getB().x << endl;
return 0;
}
Es posible tener una referencia a mala memoria. Y así, la respuesta a foo.attr; // 100% safe access?
foo.attr; // 100% safe access?
, no es. Considere el siguiente ejemplo:
int &something() {
int i = 5, &j = i;
return j; // Return reference to local variable. This is destroyed at end of scope.
}
int main() {
int &k = something(); // Equivalent to getFoo()
std::cout << k << endl; // Using this reference is undefined behavior.
return 0;
}
La referencia k no apunta a memoria legítima. Pero esto todavía se compilará. No hay caso de que esto pueda suceder, sin embargo, donde el programador no ha cometido un error. En este caso, la función something()
está escrita incorrectamente y tendrá que ser arreglada. No hay manera, o razón para comprobar esto. Si una función devuelve una referencia incorrecta, lo único que puede (y debe) hacer es corregir la función ofensiva.
Si es posible.
Foo& Fr = *(Foo*)nullptr;
Técnicamente, esto ya es un comportamiento indefinido para desreferenciar ese puntero. Pero lo más probable es que no resulte en ningún error observable. Esto probablemente lo hará sin embargo:
Fr.attr = 10;
Sin embargo, como Jonathan Wakely señala en los comentarios, no hay razón para que usted busque un caso como este. Si una función devuelve una referencia no válida, esa función se rompe y debe corregirse. Su código de uso no se rompe si se asume que la referencia es válida. Sin embargo, una referencia válida puede volverse inválida (aunque no nula) en un código perfectamente legítimo, como se menciona en la respuesta de David Schwartz. Pero no hay forma de que compruebes esto. Simplemente necesita saber en qué casos puede suceder y luego dejar de usar la referencia.
Una referencia debe referirse a un objeto válido cuando esa referencia está asentada. Este es un requisito estándar de C ++ y cualquier código que lo infrinja es UB y podría hacer literalmente cualquier cosa.
Sin embargo, es perfectamente legal destruir el objeto al que se refiere una referencia después de que esa referencia está asentada. En ese punto, acceder a la referencia es ilegal. Por ejemplo:
std::vector<int> j;
j.push_back(3);
int& k = j.front(); // legal, object exists now
j.clear(); // legal, object may be destroyed while reference exists
k++; // illegal, destruction of object invalidates reference
Esto significa que una función que devuelve una referencia siempre debe devolver una referencia que sea válida cuando se devuelva. Esta es la razón por la que llamar al front
en un vector vacío es UB: una referencia debe ser válida cuando está sentado. Sin embargo, a menudo habrá condiciones que posteriormente pueden invalidar esa referencia, y debe comprender cuáles son esas condiciones si planea intentar esconder la referencia y acceder a ella más adelante.
En general, debe asumir que no es seguro esconder una referencia devuelta y acceder a ella más tarde, a menos que sepa que la referencia seguirá siendo válida. Por ejemplo, std::vector
explica cuidadosamente en qué condiciones se puede invalidar una referencia en el contenedor, y eso incluye una llamada posterior a push_back
. Así que esto está roto:
std::vector<int> j;
j.push_back(3);
int &first = j.front();
j.push_back(4);
int &second = j.back();
if (first == second) // illegal, references into container are invalidated by push_back
Pero esto está bien:
std::vector<int> j;
j.push_back(3);
j.push_back(4);
int &first = j.front();
int &second = j.back();
if (first == second) // legal, references into container stay valid