【问题标题】:Iterating through a map randomly causes segfault [closed]随机遍历地图会导致段错误[关闭]
【发布时间】:2013-01-28 13:24:11
【问题描述】:

我一直在努力解决一个相当令人费解的问题:当我的实体管理器在更新循环中遍历实体映射时,我偶尔会遇到分段错误。奇怪的是,这种情况并非一直发生。有时它会在加载时崩溃,有时我可以在出现段错误之前在应用程序状态(以及多次加载和卸载实体)之间切换几次。在调试模式下,我似乎也遇到了更多的段错误。我的实体由指向 Behavior 和 Drawable 类的指针组成。

段错误后我的调用堆栈:

#0 6FCB4986 libstdc++-6!_ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base() (C:\MinGW\bin\libstdc++-6.dll:??)
#1 0040A1D7 std::_Rb_tree_iterator<std::pair<unsigned int const, Entity*> >::operator++(this=0x28fe94) (c:/mingw/bin/../lib/gcc/mingw32/4.6.2/include/c++/bits/stl_tree.h:196)
#2 00401F55 EntityManager::onLoop(this=0x417238) (C:\Users\Nelarius\Documents\Kurssit\Miinaharava\src\engine\EntityManager.cpp:75)
#3 00401640 App::onLoop(this=0x417040) (C:\Users\Nelarius\Documents\Kurssit\Miinaharava\src\engine\App.cpp:38)
#4 0040160C App::execute(this=0x417040) (C:\Users\Nelarius\Documents\Kurssit\Miinaharava\src\engine\App.cpp:30)
#5 00403BD7 main(argc=1, argv=0x642908) (C:\Users\Nelarius\Documents\Kurssit\Miinaharava\src\main.cpp:15)

这是我的更新循环:

void EntityManager::onLoop()
{
    std::map<const unsigned int, Entity*>::iterator it;

    for (it = _gameObjects.begin(); it != _gameObjects.end(); it++)
    {
        Behavior* behavior = it->second->getBehavior();
        if (behavior)
        {
            behavior->update();
        }
    }
}

我得到了段错误

for (it = _gameObjects.begin(); it != _gameObjects.end(); it++)

顺便问一下,当我不使用任何多线程时,有两个线程是正常的吗?我正在查看Code::Blocks 调试窗口,碰巧看到线程监视窗口中有两个线程(不过只有一个处于活动状态)。

【问题讨论】:

  • 欢迎光临车祸现场。
  • 你在地图中有一对Entity(即it->second)实际上是NULL吗?但是,这听起来像是内存损坏。但是您需要告诉我们更多信息。
  • @MariusBancila:不一定是 NULL,这很容易检测到。悬空会更糟。他正在使用原始指针,因此指针可能不是 NULL,而是指向已销毁的对象。取消引用这样的指针是未定义的行为。这就是应该使用智能指针的原因。
  • 我向你保证这不是“随机的”。
  • 我希望我的实体管理器类不能引用悬空指针。我刚刚添加了一个检查它->second == NULL,但它似乎没有捕获任何东西,而且我仍然在for (it = _gameObjects.begin(); it != _gameObjects.end(); it++) 遇到段错误

标签: c++ map iterator segmentation-fault


【解决方案1】:

这类事情通常归结为behavior-&gt;update() 能够通过一系列嵌套函数调用导致_gameObjects 容器被修改(例如,如果在游戏对象中检测到某些条件导致出于任何原因创建或删除游戏对象)。

如果您从 map 中删除了一个元素,这可能会使您的迭代器无效并中断您的循环,并且很难在这样的“内核”代码中发现。

一个常见的解决方案是为循环复制游戏对象列表。当然,您不会复制对象本身,但您可以保护它们的列表在更新运行过程中不被改变。

它在调度方面也“更公平”——您基本上避免了由心怀不满的自我复制游戏对象发起的 DDoS 攻击的可能性。 :)

【讨论】:

  • 您说的完全正确,我现在因为没有看到这一点而感到有点愚蠢。我不确定地图在迭代过程中是如何发生变异的,但肯定是这样。遍历本地副本不会导致段错误。
  • “这会使你的迭代器失效并打破你的循环”——maps 中的迭代器不会因其他人向map [23.1.2/8] 添加元素而失效。跨度>
  • @Yakk:添加不是唯一的可能性。可以删除“当前”元素。请参阅this question 了解所有迭代器失效规则。
  • @Non-StopTimeTravel 查看编辑:说“例如,您可以添加元素”,然后是“可以使您的迭代器无效”,措辞不好。
【解决方案2】:

您正在迭代非本地数据,然后调用非本地函数。

创建本地地图。 swap 将类映射到其中。迭代该本地地图。断言迭代完成后类映射未更改(您还需要断言从类映射中删除内容的所有尝试都成功)。

然后将本地地图交换回类的地图。

这将告诉您崩溃是否是由于您的不良设计造成的。即使没有生成任何断言,该设计仍然存在问题,因为远离上述代码的无害代码更改可能会导致发生上述问题:代码正确性的非局部性会导致问题。代码正确性的非局部性带有断言可以通过充分的测试来容忍。

一般的回调问题要求您对注册回调的含义做出妥协,并涉及以类似于多线程代码的方式思考问题。如果您不愿意妥协,代码复杂性会变得非常高。建议使用智能指针来简化事情。

【讨论】:

  • 解决方案不错;这个问题的解释几乎不存在。 +1 -1 = 0
  • (虽然我不会断言不修改;这有点限制不是吗?)
  • Don't do that, period. 是的,但您的回答无法解释为什么
猜你喜欢
  • 2015-09-03
  • 1970-01-01
  • 1970-01-01
  • 2017-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多