He encontrado un comportamiento muy extraño (en clang y GCC) en la siguiente situación. Tengo un vector, nodes
con un elemento, una instancia de clase Node
. Luego llamo a una función nodes[0]
que agrega un nuevo Node
al vector. Cuando se agrega el nuevo nodo, los campos del objeto que llama se restablecen. Sin embargo, parecen volver a la normalidad una vez que la función ha finalizado.
Creo que este es un ejemplo reproducible mínimo:
#include <iostream>
#include <vector>
using namespace std;
struct Node;
vector<Node> nodes;
struct Node{
int X;
void set(){
X = 3;
cout << "Before, X = " << X << endl;
nodes.push_back(Node());
cout << "After, X = " << X << endl;
}
};
int main() {
nodes = vector<Node>();
nodes.push_back(Node());
nodes[0].set();
cout << "Finally, X = " << nodes[0].X << endl;
}
Que salidas
Before, X = 3
After, X = 0
Finally, X = 3
Aunque esperarías que X permanezca sin cambios por el proceso.
Otras cosas que he probado:
- Si elimino la línea que agrega un
Node
interiorset()
, genera X = 3 cada vez. - Si creo un nuevo
Node
y lo llamo en ese (Node p = nodes[0]
), la salida es 3, 3, 3 - Si creo una referencia
Node
y la llamo en ese (Node &p = nodes[0]
), entonces la salida es 3, 0, 0 (¿quizás esta es porque la referencia se pierde cuando el vector cambia de tamaño?)
¿Es este comportamiento indefinido por alguna razón? ¿Por qué?
reserve(2)
al vector antes de llamarlo,set()
este sería un comportamiento definido. Pero escribir una función comoset
esa requiere que el usuarioreserve
tenga el tamaño adecuado antes de llamarla para evitar un comportamiento indefinido, es un mal diseño, así que no lo haga.Respuestas:
Su código tiene un comportamiento indefinido. En
El acceso a
X
es realmentethis->X
ythis
es un puntero al miembro del vector. Cuando lo hacenodes.push_back(Node());
, agrega un nuevo elemento al vector y ese proceso se reasigna, lo que invalida todos los iteradores, punteros y referencias a elementos en el vector. Eso significaestá utilizando un
this
que ya no es válido.fuente
push_back
comportamiento ya indefinido (ya que estamos en una función miembro con invalidadothis
) o UB ocurre la primera vez que usamos elthis
puntero? ¿Sería posible es decirreturn 42;
?nodes
es independiente de unaNode
instancia, por lo que no hay UB en las llamadaspush_back
. El UB está usando el puntero inválido después.void set(Node* this)
, no está indefinido pasarle un puntero no válido ofree()
en la función. No estoy seguro, pero imagino que incluso((Node*) nullptr)->set()
se define si no se usathis
y el método no es virtual.((Node *) nullptr)->set()
esté bien, ya que esto hace referencia a un puntero nulo (se ve claramente cuando se escribe de manera equivalente como(*((Node *) nullptr)).set();
).reasignará el vector, cambiando así la dirección de
nodes[0]
, perothis
no se actualiza.intente reemplazar el
set
método con este código:observe cómo
&nodes[0]
es diferente después de llamarpush_back
.-fsanitize=address
captará esto e incluso le dirá en qué línea se liberó la memoria si también compila-g
.fuente