Valgrind: 无效的8字节读取错误

3
当我通过valgrind运行我的程序时,我得到了以下结果。
==29852== Invalid read of size 8
==29852==    at 0x4EDEA50: std::_Rb_tree_increment(std::_Rb_tree_node_base const*) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==29852==    by 0x414EEA: std::_Rb_tree_const_iterator<std::pair... >::operator++() (stl_tree.h:284)
==29852==    by 0x4268CF: Tree::removeConstantsPair(std::set...) (Tree.h:65)
==29852==    by 0x4239C4: yy_reduce(yyParser*, int) (parser.y:251)
==29852==    by 0x425F6D: Parse(void*, int, Token*, Tree*) (parser.c:1418)
==29852==    by 0x404837: main (main.cpp:95)

Tree.h中的第65行是

inline void removeConstantsPair(set<pair<string, string>>& vec){
    set<string>::iterator itr; 
    for(auto &v : vec){ //This is line 65
        itr = domainList.find(v.first);
        if(itr != domainList.end())
            vec.erase(v);
    }
}

然而,泄漏摘要表明没有丢失的内存。据我所知,如果我从已释放的内存中读取,则会发生无效的读取,因此在我的情况下,&vec 必须先被释放。尽管如此,我的程序仍然能够运行而不崩溃。有人可以解释为什么会出现内存读取错误吗?

2
std::set::erase() 会使被删除的迭代器失效。如果对已经被删除的迭代器进行增量操作,就会出现错误报告(使用了 operator++())。 - PaulMcKenzie
3个回答

5
问题最可能是由以下行引起的:

        vec.erase(v);

使用范围for循环并从容器中删除项不是一个好主意。将您的循环更改为:
for ( auto iter = vec.begin(); iter != vec.end(); /* Empty on purpose*/ )
{
   if(domainList.find(iter->first) != domainList.end())
   {
      iter = vec.erase(iter);
   }
   else
   {
      ++iter;
   }
}

成功了!有没有任何g++编译器标志可以捕捉到这种行为? - Pdksock
@SamidhT,我所知道的没有。 - R Sahu

2
下面的小程序在使用Visual Studio编译器运行时会显示错误:
#include <string>
#include <set>
#include <map>

std::set<std::string> domainList = {"abc", "123", "456"};

using namespace std;

void removeConstantsPair(set<pair<string, string>>& vec)
{
    set<string>::iterator itr; 
    for(auto &v : vec)
    { 
        itr = domainList.find(v.first);
        if(itr != domainList.end())
            vec.erase(v);  // <--erasing this iterator makes it invalid
    }
}

int main()
{
    std::set<std::pair<string, string>> vec = {make_pair("abc", "xyz"), 
                                               make_pair("456", "xyz"),
                                               make_pair("000", "xyz")};
    removeConstantsPair(vec);
}

Visual Studio 调试运行时在 for 循环中尝试递增已抹消的迭代器时,会出现“表达式映射/集合迭代器不可递增”的断言。

enter image description here

所以解决办法是确保要增加的迭代器不是被删除的那个。
void removeConstantsPair(set<pair<string, string>>& vec)
{
    set<string>::iterator itr;
    auto iterSet = vec.begin();
    while (iterSet != vec.end())
    {
        itr = domainList.find((*iterSet).first);
        if (itr != domainList.end())
        {
            auto erasedIter = iterSet;
            ++iterSet;
            vec.erase(erasedIter);
        }
        else
            ++iterSet;
    }
}

1
"无效内存读取"除了问题中所述的原因外,还可能发生其他许多情况。
例如:通过new请求分配内存通常会分配比新类实例所需的内存略多一些。例如,某个C++实现可能每16字节分配一次内存。因此,对于一个sizeof返回12的类的实例的new实际上将分配16字节,并且尝试读取实际实例化对象结尾之后的内容将被valgrind正确地标记为无效内存读取。"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接