添加到 std::vector 时类字段的奇怪行为
Posted
技术标签:
【中文标题】添加到 std::vector 时类字段的奇怪行为【英文标题】:Weird behaviour with class fields when adding to a std::vector 【发布时间】:2020-06-15 02:59:44 【问题描述】:在以下情况下,我发现了一些非常奇怪的行为(在 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 在该过程中保持不变。
我尝试过的其他事情:
如果我删除在set()
中添加Node
的行,那么它每次都会输出X = 3。
如果我创建一个新的 Node
并在其上调用它 (Node p = nodes[0]
),则输出为 3、3、3
如果我创建一个引用 Node
并在其上调用它 (Node &p = nodes[0]
),则输出为 3、0、0(也许这是因为当向量调整大小时引用丢失了?)
这是出于某种原因未定义的行为吗?为什么?
【问题讨论】:
见en.cppreference.com/w/cpp/container/vector/push_back。如果您在调用set()
之前在向量上调用reserve(2)
,这将被定义为行为。但是写一个像set
这样的函数,要求用户在调用它之前适当地reserve
足够的大小以避免未定义的行为是不好的设计,所以不要这样做。
【参考方案1】:
您的代码具有未定义的行为。在
void set()
X = 3;
cout << "Before, X = " << X << endl;
nodes.push_back(Node());
cout << "After, X = " << X << endl;
对X
的访问实际上是this->X
,而this
是指向向量成员的指针。当您执行nodes.push_back(Node());
时,您将一个新元素添加到向量中并且该进程重新分配,这使向量中的元素的所有迭代器、指针和引用无效。这意味着
cout << "After, X = " << X << endl;
正在使用不再有效的this
。
【讨论】:
是在调用push_back
已经未定义的行为(因为我们在一个无效的this
的成员函数中)还是在我们第一次使用this
指针时发生UB?是否有可能即return 42;
?
@n314159 nodes
独立于Node
实例,因此在调用push_back
时没有UB。 UB 之后使用了无效的指针。
@n314159 一个很好的概念化方法是想象一个函数void set(Node* this)
,向它传递一个无效指针或在函数中传递给free()
并不是未定义的。我不确定,但我想如果您不使用 this
并且该方法不是虚拟的,那么即使 ((Node*) nullptr)->set()
也已定义。
我不认为((Node *) nullptr)->set()
可以,因为这会取消引用一个空指针(当你将它等同于(*((Node *) nullptr)).set();
时,你会清楚地看到那个kore)。
.push_back()
可能使所有指针、引用和迭代器无效。是.capacity() == .size()
?是的,看起来像。【参考方案2】:
nodes.push_back(Node());
将重新分配向量,从而改变nodes[0]
的地址,但this
没有更新。
尝试用以下代码替换 set
方法:
void set()
X = 3;
cout << "Before, X = " << X << endl;
cout << "Before, this = " << this << endl;
cout << "Before, &nodes[0] = " << &nodes[0] << endl;
nodes.push_back(Node());
cout << "After, X = " << X << endl;
cout << "After, this = " << this << endl;
cout << "After, &nodes[0] = " << &nodes[0] << endl;
注意在调用push_back
后&nodes[0]
的不同之处。
-fsanitize=address
会捕捉到这一点,如果你也使用-g
编译,甚至会告诉你内存在哪一行被释放。
【讨论】:
以上是关于添加到 std::vector 时类字段的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章
std::vector<QString,int*> 的奇怪行为