Я обнаружил очень странное поведение (на Clang и GCC) в следующей ситуации. У меня есть вектор, nodes
с одним элементом, экземпляр класса Node
. Затем я вызываю функцию, nodes[0]
которая добавляет новый Node
вектор. При добавлении нового узла поля вызывающего объекта сбрасываются! Однако они, похоже, снова возвращаются в нормальное состояние после завершения функции.
Я считаю, что это минимальный воспроизводимый пример:
#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;
}
Какие выводы
Before, X = 3
After, X = 0
Finally, X = 3
Хотя вы ожидаете, что X останется неизменным в процессе.
Другие вещи, которые я пробовал:
- Если я удаляю строку, которая добавляет
Node
внутреннюю частьset()
, то каждый раз выводится X = 3. - Если я создаю новый
Node
и вызываю его на то (Node p = nodes[0]
), то результат будет 3, 3, 3 - Если я создаю ссылку
Node
и вызываю ее дляNode &p = nodes[0]
this ( ), то вывод будет равен 3, 0, 0 (возможно, это происходит потому, что ссылка теряется при изменении размера вектора?)
Это неопределенное поведение по какой-то причине? Почему?
reserve(2)
вектор перед вызовом,set()
это было бы определенным поведением. Но написание подобной функцииset
требует от пользователяreserve
достаточно подходящего размера перед вызовом, чтобы избежать неопределенного поведения, и это плохой дизайн, поэтому не делайте этого.Ответы:
Ваш код имеет неопределенное поведение. В
Доступ к
X
действительноthis->X
иthis
является указателем на член вектора. Когда выnodes.push_back(Node());
добавляете новый элемент в вектор, и этот процесс перераспределяет, который делает недействительными все итераторы, указатели и ссылки на элементы в векторе. Это значитиспользует,
this
который больше не действителен.источник
push_back
уже неопределенное поведение (поскольку мы находимся в функции-члене с недействительнымиthis
) или UB возникает при первом использованииthis
указателя? Будет ли это возможноreturn 42;
?nodes
не зависит отNode
экземпляра, поэтому в вызове нет UBpush_back
. UB впоследствии использует неверный указатель.void set(Node* this)
, которая не является неопределенной, чтобы передать ей недопустимый указатель илиfree()
функцию в функции. Я не уверен, но я представляю, что даже((Node*) nullptr)->set()
определяется, если вы не используете,this
и метод не является виртуальным.((Node *) nullptr)->set()
это нормально, так как это разыменовывает нулевой указатель (вы видите это kore ясно, когда пишете это эквивалентно как(*((Node *) nullptr)).set();
).перераспределяет вектор, изменяя адрес
nodes[0]
, ноthis
не обновляется.попробуйте заменить
set
метод следующим кодом:обратите внимание, как
&nodes[0]
отличается после звонкаpush_back
.-fsanitize=address
поймает это и даже скажет вам, по какой строке была освобождена память, если вы также скомпилируете-g
.источник