【问题标题】:Strange output from dereferencing pointers into a vector将指针解引用为向量的奇怪输出
【发布时间】:2020-03-21 19:25:38
【问题描述】:

我正在用 C++ 编写一个程序,我需要一个 2d 指针网格,这些指针指向存储在向量中的对象。我测试了程序的某些部分,并在输出中看到了奇怪的结果。

我将对象更改为整数并删除了所有不必要的内容以将其缩减为下面的代码 sn-p,但我仍然得到一个奇怪的输出。

vector<vector<int*>> lattice(10, vector<int*>(10));//grid of pointers

vector<int> relevant;//vector carrying actual values onto which pointers will point

for(int i = 0; i<10; i++){
    int new_integer = i;
    relevant.push_back(new_integer);//insert integer into vector
    lattice[0][i] = &relevant[i];//let pointer point onto this value
}

//OUTPUT
for(int j = 0; j<10; j++){
    cout<<*lattice[0][j]<<" ";
    cout<<relevant[j]<<endl;
}

我得到这样的奇怪输出:

19349144 0
19374040 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9

此外,我的输出在每次运行时都会发生变化,具体取决于我制作网格的大小。

我希望左边和右边的所有值都是相等的,我想关于指针有一些基本的东西我还没有理解,如果这是一个非常基本的问题,很抱歉。

有人能解释一下为什么我会得到一些网格值的奇怪输出吗?

【问题讨论】:

  • 未定义的行为。向量可以在push_backs 之间重新分配您指向的内部内存。
  • 对不起,你能不能解释的详细一点,我不明白。
  • 如果你尝试cout&lt;&lt;*(lattice[0][j])&lt;&lt;" ";会发生什么?

标签: c++ pointers vector


【解决方案1】:

我需要一个二维指针网格,指向存储在向量中的对象

那是不可能的。或者更确切地说,这是取消引用无效地址的保证。为什么?

在任何给定时间,std::vector 都为有限数量的元素分配了足够的空间。如果您继续向其中添加元素,它最终将最大化其存储空间。在某些插入时,它将决定分配一段新的内存用于存储其数据;将现有数据移动(或复制)到新的存储区域;释放旧的存储区域;然后可以添加更多元素。

发生这种情况时,所有指向向量中对象的现有指针都将变为无效。他们指向的内存可能会继续保存以前的值,但也可能用于存储其他数据——对此没有任何保证!事实上,取消引用无效指针正式导致undefined behavior

...而且我确实看到我所描述的正是您对代码所做的事情。您的旧指针变得无效。

相反,考虑将 indices 保留在向量中而不是指针中。向向量中添加元素不会使索引失效,您可以继续使用它们。


PS - 我看到你正在使用向量的向量。这在技术上是有效的,但通常是不可取的。考虑使用矩阵类(例如来自 Eigen 库)或使用 std::make_unique() 分配一定数量的内存,然后使用它来初始化 gsl::multi_span

【讨论】:

  • 如果 OP 的方法从不改变向量的大小,他们可能会奏效。虽然我也不建议这样做。
  • @Yksisarvinen:“从不”是编程中一个非常相对的状态。明天有人会引入另一个添加并得到令人惊讶的错误。所以,你是对的,但你不建议这样做更正确。
  • 代码应该在进入push_back()循环之前reserve()向量元素。这将避免重新分配问题并且不会使指针无效
  • 关于“为什么”我想这样做:我正在编写一个非常大的模拟细胞在长网格(或者更确切地说是长走廊)上生长的模拟。我总是只需要“记住我不断增长的界面前面的一定数量的单元格,而我可以“忘记”留下的与前面的单元格不再相关的单元格。因此,我想使用网格可以说随着我不断增长的前沿而游荡的指针。我需要这个网格,因为我需要一些空间结构来实现增长算法。有什么建议如何处理这个?
  • @DeltaChief:对于看似相当复杂的计算任务,我无法给你一个体面的建议。请考虑咨询具有更多软件和数据结构设计经验的人,他们可以认真研究您的挑战。
【解决方案2】:

relevant.push_back 使指向其元素的所有指针/引用/迭代器无效(如果新大小超过其当前容量)。

因此,当您这样做时,您正在取消引用可能无效的指针

*lattice[0][j]

稍后。

对于relevant,您可以使用不会在最后插入时失效的容器,例如std::liststd::deque,而不是std::vector

(或者您可以先调用.reserve 来保留足够的容量,这样.push_back 操作的大小将永远不会超过容量,因此永远不会使指针无效,但这会带来很容易意外的风险在以后的代码更改中忽略此要求,再次导致 UB。)

【讨论】:

    【解决方案3】:

    当一个std::vector被插入时,如果它的新size()将超过它当前的capacity(),向量必须重新分配它的内部数组来腾出空间,这将使任何现有的迭代器和指向旧内存的指针失效.

    在您的示例中,您可以通过提前reserve()'ing capacity() 来避免这种情况,例如:

    vector<vector<int*>> lattice(10, vector<int*>(10));//grid of pointers
    
    vector<int> relevant;//vector carrying actual values onto which pointers will point
    
    //ADD THIS!
    relevant.reserve(10);//pre-allocate the capacity
    
    for(int i = 0; i<10; i++){
        int new_integer = i;
        relevant.push_back(new_integer);//insert integer into vector
        lattice[0][i] = &relevant[i];//let pointer point onto this value
    }
    
    //OUTPUT
    for(int j = 0; j<10; j++){
        cout<<*lattice[0][j]<<" ";
        cout<<relevant[j]<<endl;
    }
    

    或者,您可以预先分配size(),然后使用operator[] 而不是push_back(),例如:

    vector<vector<int*>> lattice(10, vector<int*>(10));//grid of pointers
    
    vector<int> relevant(10);//vector carrying actual values onto which pointers will point
    
    for(int i = 0; i<10; i++){
        int new_integer = i;
        relevant[i] = new_integer;//insert integer into vector
        lattice[0][i] = &relevant[i];//let pointer point onto this value
    }
    
    //OUTPUT
    for(int j = 0; j<10; j++){
        cout<<*lattice[0][j]<<" ";
        cout<<relevant[j]<<endl;
    }
    

    【讨论】:

    • 您建议他们将指针保留在向量中,这给 OP 提供了糟糕的建议,这依赖于 reserve() 的幸运中断,最初可以防止指针失效。向量可能会在稍后更改,或者在代码的其他地方更改,并且指针将再次无效。将指针保存在向量中通常是错误的。向量为const 的范围除外。 @DeltaChief:仅供参考。
    猜你喜欢
    • 2013-03-31
    • 2012-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多