大小为 4 的迭代器无效读取

Iterator invalid read of size 4

为什么 Valgrind 会在下一行中指示 Invalid read of size 4

for (map<uint16_t, SPacket *>::iterator it = m_PacketMap.begin() ; it != m_PacketMap.end(); ++it) 
{
    if (it->first < ackNumber)
    {
        if (it->second->data) delete [] it->second->data;
        if (it->second) delete it->second;
        m_PacketMap.erase(it);
    }
}

我在循环之前验证 m_PacketMap.size() > 0 并在循环之前临时添加调试以验证 m_PacketMap 内容,但一切看起来都符合预期。这是 Valgrind 错误消息,RadioManager.cpp:1042 是上面的行:

==5535== Invalid read of size 4
==5535==    at 0x421EBE5: std::_Rb_tree_increment(std::_Rb_tree_node_base*) (in /usr/lib/i386-linux-gnu/libstdc++.so.6.0.16)
==5535==    by 0x80AD20D: RadioManager::DecodeAcknowledgementNumber(unsigned char*, unsigned int) (RadioManager.cpp:1042)

这就是 SPacket 和 m_PacketMap 的定义方式

typedef struct SPacket
{
    uint8_t * data;
    size_t size;
    timeval tval;
} SPacket;
map<uint16_t, SPacket *> m_PacketMap;

我的迭代器是否存在问题,_Rb_tree_increment 中可能存在问题,或者完全是其他问题?

for 循环中删除容器元素必须小心...

你必须做到:

map<uint16_t, SPacket *>::iterator it = m_PacketMap.begin()
while ( it != m_PacketMap.end() ) 
{
    if (it->first < ackNumber)
    {
        if (it->second->data) delete [] it->second->data;
        if (it->second) delete it->second;
        // it updated by erase, no need to increment
        it = m_PacketMap.erase(it);
    }
    else
    {
        // move to next item
        ++it;
    }
}

这就是必须编写删除容器某些元素的循环的方式(对于集合、向量...同样适用)。

对于地图,上面的代码仅适用于 C++11。如果你使用更早的版本,根据this post(未测试),你应该这样做:

for ( map<uint16_t, SPacket *>::iterator it = m_PacketMap.begin(); it != m_PacketMap.end();  ) 
{
    if (it->first < ackNumber)
    {
        if (it->second->data) delete [] it->second->data;
        if (it->second) delete it->second;
        // it updated by erase, no need to increment
        m_PacketMap.erase(it++);
    }
    else
    {
        // move to next item
        ++it;
    }
}

因为擦除项目然后递增它(你最终用你的 for 循环做的事情)会让你松散元素并可能传递 m_PacketMap.end() (然后让你的循环覆盖项目超出容器的限制)如果您从容器中删除最后一个元素。

你擦除一个元素,这使得 it 无效,然后你增加 it。您不能增加无效的迭代器。

使用 -D_GLIBCXX_DEBUG 重建您的代码将启用 libstdc++ 调试模式,这将在无效的迭代器操作时中止。

与 valgrind 错误无关,在使用 delete 之前不需要测试指针是否为空,因为在空指针上使用 delete 是安全的(它检查空指针无论如何,所以你只是添加了不必要的重复检查。