我正在尝试调试一个旧游戏,但遇到了内存管理的一个棘手问题。这是正在发生的事情的片段:
#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
不需要。
我的问题是:
for
在
v
之前被破坏,为什么
m
循环仍然执行?如何避免这种情况?
v
和
m
属于不同的模块,是分开编译的。如何确保在链接时在
m
中的
v
之后初始化
CMakeLists.txt
?
谁能解释一下,如果这是
for
在v
之前被破坏,为什么m
循环仍然执行?
未定义的行为! 任何事情都可能发生,包括看似的正确行为。
在这种情况下,可能发生的情况是,当迭代器返回时,vector
的内部内存尚未被覆盖,因此它们仍然引用已被释放的动态内存,这可以解释为什么在取消引用
it
迭代器之前,不会发生崩溃。您根本无法在对象被销毁后访问该对象,并且在您的示例中,
v
在调用
~Main()
之前被销毁。由于
~Main()
使用
v
,因此
v
的生存时间必须比
m
更长。