Znalazłem bardzo dziwne zachowanie (na clang i GCC) w następującej sytuacji. Mam wektor, nodes
z jednym elementem, instancją klasy Node
. Następnie wywołuję funkcję, nodes[0]
która dodaje nową wartość Node
do wektora. Po dodaniu nowego węzła pola obiektu wywołującego są resetowane! Wydają się jednak wracać do normy po zakończeniu funkcji.
Uważam, że jest to minimalny, powtarzalny przykład:
#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;
}
Które wyjścia
Before, X = 3
After, X = 0
Finally, X = 3
Chociaż można oczekiwać, że X pozostanie niezmieniony przez ten proces.
Inne rzeczy, których próbowałem:
- Jeśli usunę linię, która dodaje
Node
wnętrzeset()
, to za każdym razem wyprowadza X = 3. - Jeśli utworzę nowy
Node
i wywołam go na tym (Node p = nodes[0]
), wówczas wynikiem będzie 3, 3, 3 - Jeśli utworzę odwołanie
Node
i wywołam go na tym (Node &p = nodes[0]
), wówczas wynikiem będzie 3, 0, 0 (być może jest to spowodowane tym, że odwołanie zostało utracone, gdy wektor zmienia rozmiar?)
Czy z jakiegoś powodu jest to niezdefiniowane zachowanie? Dlaczego?
reserve(2)
wektor przed wywołaniem,set()
byłoby to zdefiniowane zachowanie. Ale napisanie takiej funkcjiset
wymaga od użytkownika odpowiedniegoreserve
rozmiaru przed wywołaniem jej, aby uniknąć nieokreślonego zachowania jest złym projektem, więc nie rób tego.Odpowiedzi:
Twój kod ma niezdefiniowane zachowanie. W
Dostęp do
X
jest naprawdęthis->X
ithis
jest wskaźnikiem do elementu wektora. Gdy to zrobisznodes.push_back(Node());
, dodasz nowy element do wektora, a proces ten zostanie ponownie przydzielony, co unieważnia wszystkie iteratory, wskaźniki i odniesienia do elementów w wektorze. To znaczyużywa
this
nieważnego.źródło
push_back
już niezdefiniowanego zachowania (ponieważ jesteśmy wtedy w funkcji członka z unieważnioną funkcjąthis
) czy też UB występuje przy pierwszym użyciuthis
wskaźnika? Czy byłoby możliwe np.return 42;
?nodes
jest niezależny odNode
instancji, więc nie ma UB w wywołaniupush_back
. UB używa następnie nieprawidłowego wskaźnika.void set(Node* this)
, nie jest niezdefiniowane, aby przekazać jej nieprawidłowy wskaźnik lub dofree()
funkcji. Nie jestem pewien, ale wyobrażam sobie, że nawet((Node*) nullptr)->set()
jest zdefiniowane, jeśli nie używasz,this
a metoda nie jest wirtualna.((Node *) nullptr)->set()
było w porządku, ponieważ to wyklucza wskaźnik zerowy (korelię tę wyraźnie widać, gdy pisze się ją równoważnie jako(*((Node *) nullptr)).set();
).ponownie przydzieli wektor, zmieniając w ten sposób adres
nodes[0]
, alethis
nie zostanie zaktualizowany.spróbuj zastąpić
set
metodę tym kodem:zwróć uwagę, jak
&nodes[0]
różni się po telefonowaniupush_back
.-fsanitize=address
złapie to, a nawet powie, w której linii pamięć została zwolniona, jeśli również się skompilujesz-g
.źródło