【问题标题】:Is valgrind crazy or is this is a genuine std map iterator memory leak?valgrind 是疯了还是这是真正的 std map 迭代器内存泄漏?
【发布时间】:2010-05-20 10:15:47
【问题描述】:

嗯,总的来说,我对 Valgrind 和内存泄漏分析器非常陌生。而且我必须说,当您开始使用它们时会有些害怕,因为您无法停止想知道您之前可能有多少漏洞未解决!

由于我不是 c++ 程序员的经验,我想检查一下这是否肯定是内存泄漏还是 Valgrind 的误报?

typedef std::vector<int> Vector;
typedef std::vector<Vector> VectorVector;
typedef std::map<std::string, Vector*> MapVector;
typedef std::pair<std::string, Vector*> PairVector;
typedef std::map<std::string, Vector*>::iterator IteratorVector;

VectorVector vv;
MapVector m1;
MapVector m2;

vv.push_back(Vector());
m1.insert(PairVector("one", &vv.back()));

vv.push_back(Vector());
m2.insert(PairVector("two", &vv.back()));

IteratorVector i = m1.find("one");
i->second->push_back(10);
m2.insert(PairVector("one", i->second));

m2.clear();
m1.clear();
vv.clear();

这是为什么呢? clear 命令不应该调用每个对象和每个向量的析构函数吗?

现在在做了一些测试后,我发现了不同的泄漏解决方案:

1) 删除:

i->second->push_back(10);

2) 添加:

delete i->second;

3) 删除第二个

vv.push_back(Vector());
m2.insert(PairVector("two", &vv.back()));

使用解决方案 2) 使 Valgring 打印:10 个分配,11 个释放可以吗?

由于我没有使用 new 为什么要删除?

感谢您的帮助!

【问题讨论】:

  • 不要使用块引号来格式化代码,使用 101010 图标(或 Ctrl+K)。
  • 您对 typedefs 的使用使我无法理解代码。
  • @Marcelo:我认为现在的代码很好。 @Neil,我认为它现在很容易理解,唯一复杂的部分是我有一个向量向量,以及一个指向简单向量的映射...
  • @Alberto Toglia:为什么选择社区维基?
  • @Gorpik,因为该问题已被编辑超过 5 次。它会自动转到 CW。

标签: c++ memory map valgrind memory-leaks


【解决方案1】:

基本上是这条线导致了问题:

i->second->push_back(10);

这是因为当你这样做时 i->second 可能已经失效:

vv.push_back(Vector());

第二次。

无需调用 clear。当 vv 对象超出范围时,它将正确销毁所有对象。此外,所有地图都不拥有任何向量,因此它们的析构函数不会影响它们指向的向量。因此,您不需要使用 clear。

如果您想保持相同的整体解决方案,请为您的 vv 对象创建一个向量列表。然后插入列表不会影响已经存在的成员,您的地图将正常工作。

std::list<Vector> vv;  // insertion into this will not invalidate any other members.
                       // Thus any pointers to members you have will not become invalidated.

我个人认为你把事情复杂化了。
我认为您可以通过大大简化这一点来获得相同的结果。
如果向量没有被多个地图元素引用,那么只需将向量放入地图中。

std::map<std::string, std::vector<int> >    m1;

m1["one"].push_back(10);
m1["two"].push_back(20);

【讨论】:

    【解决方案2】:

    你在这里有未定义的行为:

    m1.insert(PairVector("one", &vv.back()));
    
    vv.push_back(Vector());
    

    Insert 使指向向量的迭代器和引用失效,这也意味着您存储在 map 中的指针在插入后基本上指向某个黑洞。

    让 Valgring 打印:10 次分配,11 次释放 可以吗?

    很奇怪,它不也打印一些关于双释放的东西吗?

    对于解决方案,我建议使用与vector 不同的容器(例如listdeque,其变异函数使迭代器无效,但引用无效)。或者你可以在向量中存储指向数据的指针(最好是智能的,但也可以是普通的),这样实际数据的地址是稳定的。

    【讨论】:

    • 它确实说明了无效删除,尽管我不知道如何摆脱调试调用,所以我只能看到错误。从现在开始我将使用 Valgrind,所以我希望在接下来的几天里能更适应它......
    • 在您引用的行中,这不是未定义的行为(尚未)。 只要不取消引用它,挂起无效指针是完全合法的。它只会在 i-&gt;second-&gt;push_back(10) 行变为未定义,向量指针实际上被取消引用。
    【解决方案3】:

    你在这里用向量做一些危险的事情。您保留了指向可能在程序执行期间无效的向量的指针。

    std::vector&lt;&gt;::push_back() 可能会使任何迭代器或对std::vector&lt;&gt; 的引用无效,如果它已经满了。由于std::vector&lt;&gt; 保证其内容将连续存储(因此您可以使用它来代替数组),因此当它需要更多内存时,它必须将自己复制到不同的内存块,而原来的内存块将变得无效。

    这意味着在您的代码中对push_back() 的所有调用(第一个除外)都会导致未定义的行为,因此这里可能会发生任何事情。

    【讨论】:

    • @Gorpik,感谢您的提示。但是告诉我一些事情,我有地图和向量的原因是,有了向量,我保证我的对象在内存中很好地对齐,并且地图用于查找具有特定名称的对象。不知不觉这不是为了与整数一起使用,而是与大型游戏对象一起使用......如果我对停滞的指针很小心,这对你有意义吗?
    • 现在我明白了,向量可以在增长时将我的对象内存移动到不同的位置,等等,使我的地图无用。所以我认为我最好为此使用一个简单的数组。
    • @Alberto Toglia:不是真的。如果您确定向量的大小,您可以在构造时指定它并且它永远不会移动。如果你不这样做,那么数组将会过度而不是增长,并且你将在核心上拥有 fandango。
    • vector 仅在您调整向量大小时移动其对象(您不能对数组执行此操作)。如果您将向量视为一个数组(如果它保持相同的大小),那很好,不会移动任何内容。您可以在向量上调用reserve() 来指定它应该预先分配多少空间。然后,您可以插入那么多对象,而无需重新分配和移动对象。但实际上,问题在于向量和指针的组合。指向存储在向量中的对象的指针是危险的(至少在调整向量大小的情况下)
    • @Gorpik,好吧,我知道我的数组不会增长。但这看起来是一个常见的问题,我的对象内存对齐是必须的,并且有映射来获取这些对象的指针也是必须的。我怎样才能让它们始终更新。我知道有一种叫做句柄的东西,但我还没有看到类似的好例子。
    猜你喜欢
    • 2014-08-18
    • 2012-07-12
    • 1970-01-01
    • 1970-01-01
    • 2019-04-02
    • 2011-06-19
    • 1970-01-01
    • 2018-05-25
    • 2011-02-20
    相关资源
    最近更新 更多