J'ai trouvé un comportement très étrange (sur clang et GCC) dans la situation suivante. J'ai un vecteur, nodes
avec un élément, une instance de classe Node
. J'appelle ensuite une fonction nodes[0]
qui ajoute une nouvelle Node
au vecteur. Lorsque le nouveau nœud est ajouté, les champs de l'objet appelant sont réinitialisés! Cependant, ils semblent redevenir normaux une fois la fonction terminée.
Je pense que c'est un exemple reproductible minimal:
#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;
}
Quelles sorties
Before, X = 3
After, X = 0
Finally, X = 3
Bien que vous vous attendiez à ce que X reste inchangé par le processus.
J'ai essayé d'autres choses:
- Si je supprime la ligne qui ajoute un
Node
intérieurset()
, elle renvoie X = 3 à chaque fois. - Si je crée un nouveau
Node
et l'appelle sur ça (Node p = nodes[0]
) alors la sortie est 3, 3, 3 - Si je crée une référence
Node
et l'appelle sur ça (Node &p = nodes[0]
) alors la sortie est 3, 0, 0 (peut-être celle-ci est parce que la référence est perdue lorsque le vecteur se redimensionne?)
Ce comportement n'est-il pas défini pour une raison quelconque? Pourquoi?
reserve(2)
le vecteur avant d'appeler,set()
ce serait un comportement défini. Mais écrire une fonction commeset
celle-ci nécessite que l'utilisateur aitreserve
suffisamment de taille avant de l'appeler afin d'éviter un comportement indéfini est une mauvaise conception, alors ne le faites pas.Réponses:
Votre code a un comportement indéfini. Dans
L'accès à
X
est vraimentthis->X
etthis
est un pointeur vers le membre du vecteur. Lorsque vous le faites,nodes.push_back(Node());
vous ajoutez un nouvel élément au vecteur et ce processus se réalloue, ce qui invalide tous les itérateurs, pointeurs et références aux éléments du vecteur. Cela signifieutilise un
this
qui n'est plus valide.la source
push_back
comportement déjà indéfini (puisque nous sommes alors dans une fonction membre avec invalidéthis
) ou UB se produit-il la première fois que nous utilisons lethis
pointeur? Serait-il possible de direreturn 42;
?nodes
est indépendant d'uneNode
instance, il n'y a donc pas d'UB dans l'appelpush_back
. L'UB utilise ensuite le pointeur non valide.void set(Node* this)
, il n'est pas indéfini de lui passer un pointeur invalide, ou de luifree()
passer dans la fonction. Je ne suis pas sûr mais j'imagine que même((Node*) nullptr)->set()
est défini si vous n'utilisez pasthis
et que la méthode n'est pas virtuelle.((Node *) nullptr)->set()
soit correct, car cela déréférence un pointeur nul (vous voyez clairement ce kore lorsque vous l'écrivez de manière équivalente(*((Node *) nullptr)).set();
).va réallouer le vecteur, changeant ainsi l'adresse de
nodes[0]
, maisthis
n'est pas mis à jour.essayez de remplacer la
set
méthode par ce code:notez comment
&nodes[0]
est différent après avoir appelépush_back
.-fsanitize=address
va attraper cela, et même vous dire sur quelle ligne la mémoire a été libérée si vous compilez également avec-g
.la source