为什么全局STL容器在销毁后返回有效的迭代器?

问题描述 投票:0回答:1

我正在尝试调试一个旧游戏,但遇到了内存管理的一个棘手问题。这是正在发生的事情的片段:

#include <vector>

extern std::vector<int> v;

int tmp;

struct Main {
    ~Main() {
        for (std::vector<int>::iterator it = v.begin();
                it != v.end();
                ++it) {
            tmp = *it;
        }
        v.clear();
    }
};

Main m;
std::vector<int> v;

int main() {
    v.push_back(1);
}

当使用

-fsanitize=address
编译时,它会报告向量的堆释放后使用
v
:

  • 内存区域由
    main.cpp:22
    分配,这是
    v
    的定义。
  • 它首先在
    __run_exit_handlers
    中释放,调用堆栈中没有任何帧属于
    main.cpp
  • 错误的内存访问
    READ of size 4
    来自
    main.cpp:12
    ,即
    tmp = *it

此片段涵盖了重现所需的所有细节:

  • 辅助类
    Main
    及其唯一实例
    Main m
    必须放置在 v 的定义之前
    。如果我交换 
    m
    v
    ,那么就不再存在堆释放后使用。
    
      特别是,
    • for
      循环如果移入
      main()
      ,不会造成任何麻烦,强调了辅助类的必要性。
  • v.push_back(1)
     为必填项。如果 
    v
     为空或者 
    v.clear()
     移动到 
    for
     循环之前,则不再存在堆释放后使用。
  • 全局变量
  • tmp
    是为了避免编译器优化
    *it
    -O0
     不需要。
这至少可以在 GCC 11 和 Clang 14 中重现,所以我认为应该归咎于代码。

我的问题是:

    谁能解释一下,如果
  • for
    v
     之前被破坏,为什么 
    m
     循环仍然执行?如何避免这种情况?
  • 在实际的游戏源码中,
  • v
    m
    属于不同的模块,是分开编译的。如何确保在链接时在 
    m
     中的 
    v
     之后初始化 
    CMakeLists.txt
c++ iterator
1个回答
1
投票
谁能解释一下,如果

for

v
 之前被破坏,为什么 
m
 循环仍然执行?

这是

未定义的行为任何事情都可能发生,包括看似的正确行为。

在这种情况下,可能发生的情况是,当迭代器返回时,

vector

的内部内存尚未被覆盖,因此它们仍然引用已被释放的动态内存,这可以解释为什么在取消引用 
it
 迭代器之前,不会发生崩溃。

您根本无法在对象被销毁后访问该对象,并且在您的示例中,

v

在调用
~Main()
之前被销毁。由于 
~Main()
 使用 
v
,因此 
v
 的生存时间必须比 
m
 更长。

© www.soinside.com 2019 - 2024. All rights reserved.