添加到 std::vector 时类字段的奇怪行为

Qq0*_*Qq0 32 c++ vector push-back stdvector undefined-behavior

在以下情况下,我发现了一些非常奇怪的行为(在 clang 和 GCC 上)。我有一个向量,nodes一个元素,一个 class 的实例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;
}
Run Code Online (Sandbox Code Playgroud)

哪些输出

#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;
}
Run Code Online (Sandbox Code Playgroud)

尽管您希望 X 在该过程中保持不变。

我尝试过的其他事情:

  • 如果我删除添加Nodeinside的行set(),那么它每次都会输出 X = 3 。
  • 如果我创建一个新的Node并在 ( Node p = nodes[0])上调用它,那么输出是 3, 3, 3
  • 如果我创建一个引用Node并在 ( Node &p = nodes[0])上调用它,那么输出为 3, 0, 0(也许这是因为当向量调整大小时引用丢失了?)

出于某种原因,这是未定义的行为吗?为什么?

Nat*_*ica 39

您的代码具有未定义的行为。在

void set(){
    X = 3;
    cout << "Before, X = " << X << endl;
    nodes.push_back(Node());
    cout << "After, X = " << X << endl;
}
Run Code Online (Sandbox Code Playgroud)

该访问X是真的this->Xthis是一个指针向量的成员。当你nodes.push_back(Node());向向量添加一个新元素时,该过程会重新分配,这会使向量中的所有迭代器、指针和元素引用无效。这意味着

cout << "After, X = " << X << endl;
Run Code Online (Sandbox Code Playgroud)

正在使用this不再有效的 a。

  • @n314159 `nodes` 独立于 `Node` 实例,因此调用 `push_back` 时没有 UB。之后 UB 使用无效指针。 (3认同)

Dut*_*n18 15

nodes.push_back(Node());
Run Code Online (Sandbox Code Playgroud)

将重新分配向量,从而更改 的地址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;
    }
Run Code Online (Sandbox Code Playgroud)

请注意&nodes[0]调用后有何不同push_back

-fsanitize=address会捕捉到这一点,甚至会告诉你内存在哪一行被释放,如果你也用-g.