添加到std :: vector时类字段的行为异常


31

在以下情况下,我发现了一些非常奇怪的行为(在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;
}

哪个输出

Before, X = 3
After, X = 0
Finally, X = 3

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

我尝试过的其他方法:

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

是否由于某种原因导致这种未定义的行为?为什么?


4
请参阅en.cppreference.com/w/cpp/container/vector/push_back。如果要reserve(2)在调用之前调用set()该向量,则将定义为行为。但是编写这样的函数set需要用户reserve在调用它之前有足够大的大小,以避免未定义的行为,这是不好的设计,所以不要这样做。
JohnFilleau

Answers:


39

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

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已经未定义的行为(因为我们当时处于具有invalidated的成员函数中this),还是在第一次使用this指针时发生UB ?可以return 42;吗?
n314159

3
@ n314159 nodesNode实例无关,因此调用中没有UB push_back。UB之后使用无效指针。
NathanOliver

@ n314159概念化此方法的一个好方法是想象一个函数void set(Node* this),不是将其传递给无效指针还是传递free()给函数中的函数。我不确定,但是我想((Node*) nullptr)->set()如果您不使用它this并且该方法不是虚拟的,甚至可以定义它。
DutChen18

我认为这样((Node *) nullptr)->set()还行,因为这会取消引用一个空指针(将kore等效地写为时,您会清楚地看到该kore (*((Node *) nullptr)).set();)。
n314159

1
@Deduplicator我更新了措辞。
NathanOliver

15
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;
    }

注意&nodes[0]调用后有什么不同push_back

-fsanitize=address会捕捉到这一点,甚至还告诉您在哪一行中释放了内存-g

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.