Ich habe in der folgenden Situation ein sehr seltsames Verhalten (bei Clang und GCC) festgestellt. Ich habe einen Vektor nodes
mit einem Element, einer Klasseninstanz Node
. Ich rufe dann eine Funktion auf nodes[0]
, die Node
dem Vektor eine neue hinzufügt . Wenn der neue Knoten hinzugefügt wird, werden die Felder des aufrufenden Objekts zurückgesetzt! Sie scheinen jedoch nach Beendigung der Funktion wieder normal zu werden.
Ich glaube, dies ist ein minimal reproduzierbares Beispiel:
#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;
}
Welche Ausgänge
Before, X = 3
After, X = 0
Finally, X = 3
Sie würden jedoch erwarten, dass X durch den Prozess unverändert bleibt.
Andere Dinge, die ich versucht habe:
- Wenn ich die Zeile entferne, die ein
Node
Inneres hinzufügtset()
, wird jedes Mal X = 3 ausgegeben. - Wenn ich ein neues erstelle
Node
und es auf dem (Node p = nodes[0]
) aufrufe, ist die Ausgabe 3, 3, 3 - Wenn ich eine Referenz erstelle
Node
und diese (Node &p = nodes[0]
) aufrufe, ist die Ausgabe 3, 0, 0 (möglicherweise liegt dies daran, dass die Referenz verloren geht, wenn die Größe des Vektors geändert wird?)
Ist das undefiniertes Verhalten aus irgendeinem Grund? Warum?
reserve(2)
den Vektor vor dem Aufruf aufgerufenset()
hätten, wäre dies ein definiertes Verhalten. Das Schreiben einer solchen Funktionset
erfordert jedoch, dass der Benutzerreserve
vor dem Aufrufen eine ausreichende Größe hat, um undefiniertes Verhalten zu vermeiden. Dies ist ein schlechtes Design. Tun Sie es also nicht.Antworten:
Ihr Code hat ein undefiniertes Verhalten. Im
Der Zugriff auf
X
ist wirklichthis->X
undthis
ist ein Zeiger auf das Mitglied des Vektors. Wenn Sie dies tunnodes.push_back(Node());
, fügen Sie dem Vektor ein neues Element hinzu, und dieser Prozess ordnet ihn neu zu, wodurch alle Iteratoren, Zeiger und Verweise auf Elemente im Vektor ungültig werden. Das bedeutetverwendet eine
this
, die nicht mehr gültig ist.quelle
push_back
bereits undefinierte Verhalten auf (da wir uns dann in einer Member-Funktion mit ungültig machenthis
) oder tritt UB auf, wenn wir denthis
Zeiger zum ersten Mal verwenden ? Wäre es möglich, dhreturn 42;
?nodes
ist unabhängig von einerNode
Instanz, sodass kein UB aufgerufen wirdpush_back
. Die UB verwendet anschließend den ungültigen Zeiger.void set(Node* this)
. Es ist nicht undefiniert, ihr einen ungültigen Zeiger oderfree()
in der Funktion zu übergeben. Ich bin nicht sicher, aber ich stelle mir vor, dass sogar((Node*) nullptr)->set()
definiert wird, wenn Sie nicht verwendenthis
und die Methode nicht virtuell ist.((Node *) nullptr)->set()
das in Ordnung ist, da dies einen Nullzeiger dereferenziert (Sie sehen das deutlich, wenn Sie es gleichwertig schreiben als(*((Node *) nullptr)).set();
).wird den Vektor neu zuweisen, wodurch die Adresse von geändert wird
nodes[0]
, aberthis
nicht aktualisiert wird.Versuchen Sie, die
set
Methode durch diesen Code zu ersetzen :Beachten Sie, wie
&nodes[0]
anders nach dem Anruf istpush_back
.-fsanitize=address
wird dies abfangen und Ihnen sogar sagen, in welcher Zeile der Speicher freigegeben wurde, wenn Sie auch mit kompilieren-g
.quelle