添加到 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 &amp;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-&gt;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)-&gt;set() 也已定义。 我不认为((Node *) nullptr)-&gt;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&amp;nodes[0] 的不同之处。

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

【讨论】:

以上是关于添加到 std::vector 时类字段的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

std::vector<QString,int*> 的奇怪行为

奇怪的 std::vector::reverse_iterator 和字符串行为

C++ Vector 奇怪的行为

这是 std::vector 的正常行为吗?

标准 C++ 线程 ID - 奇怪的行为

GCC的std :: sort与lambdas的不稳定行为