【问题标题】:Why erasing element from map while looping it is not causing crash everytime it happens?为什么在循环时从地图中擦除元素不会在每次发生时都导致崩溃?
【发布时间】:2022-01-13 00:43:19
【问题描述】:

所以,这是我的功能

void Timers::RemoveTimer(DWORD id)
{
    auto it = m_mapTimers.begin();
    for ( ; it != m_mapTimers.end(); ++it) {
        if (it->first.second == id) {
            m_mapTimers.erase(it);
        }
    }
}

它显然不应该以这种方式删除元素,但我感兴趣的是,为什么崩溃不是恒定的?程序可以调用这个函数几千次,直到它崩溃。

另外,还有一个附带问题,为什么在 c++98 上没有崩溃?我在移植到 c++20 后得到它们,没有对类进行任何更改。

【问题讨论】:

  • en.cppreference.com/w/cpp/container/map/erase : 对已擦除元素的引用和迭代器无效。其他引用和迭代器不受影响。 如果您的程序有未定义的行为(因为您在删除相应的 map 元素后没有使用 it 遵循失效规则),那么难以解释的行为是可能的。经过检查的调试版本可以帮助发现此类错误。但在 C++ 中,您主要负责阅读、理解和遵循文档。一般来说,试图解释未定义的行为是没有意义的
  • @Phil1970 我知道我不应该在未定义的行为上投入太多时间,但这对我来说真的很困惑。所以我做了一些测试,结果证明它只会在只有一个元素要擦除时才会崩溃,至少现在我知道为什么它不是恒定的。现在我唯一的问题是,为什么当我使用 boost 时它没有在 c++98 上崩溃?
  • @Alkyone 如果您想弄清楚这一点,您需要了解更多有关导致崩溃的原因以及不会导致崩溃的原因。 (通常崩溃是由处理器试图取消引用未映射到任何有效页面的虚拟内存地址引起的,或者由一些库代码注意到未满足先决条件并触发断言失败引起的;它在这种情况下可能是其中任何一个)。特别是,糟糕的内存使用(例如 use-after-free)不会必然导致崩溃,例如如果读/写的内存位置恰好映射到有效/可访问的物理页面。
  • @JeremyFriesner 好吧,崩溃前的最后一个电话是__tree_is_left_child,它是从__tree_iterator& operator++()__tree_next_iter 调用的,所以我的猜测是,当地图中的最后一个元素被删除时,它会尝试访问下一个但没有?
  • @Alkyone 似乎是合理的;在 valgrind(或类似的)下运行错误程序可能会产生一些线索。

标签: c++ clang c++20 c++98


【解决方案1】:

要安全地从容器中移除元素,你应该这样写:

void Timers::RemoveTimer(DWORD id)
{
    auto it = m_mapTimers.begin();
    for ( ; it != m_mapTimers.end(); ) {
        if (it->first.second == id) {
            it = m_mapTimers.erase(it);
            continue;
        }
        it++;
    }
}

至于您的问题,如果该代码通过尝试访问它而违反了不属于它的内存段,则它可能会崩溃。 这取决于你访问的内存在地址空间中的位置,每次程序运行时都不一样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-12
    • 1970-01-01
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多