【问题标题】:double free or corruption when trying to erase in unordered_map尝试在 unordered_map 中擦除时双重释放或损坏
【发布时间】:2019-03-17 18:48:06
【问题描述】:

我有一个继承自类案例的类 Block:

class Case {
public:
    Case(sf::Vector2f const& pos, sf::IntRect const& textureRect, int type = 1);

protected:
    int type_;

    sf::Vector2f pos_;
    sf::FloatRect hitBox_;
    sf::IntRect textureRect_;
};

class Block : public Case {

public:
    Block(sf::Texture* const& texture, sf::Vector2f const& pos, sf::IntRect const& textureRect, int const& type = 2);

    sf::Sprite& getSprite();

private:
    std::shared_ptr<sf::Texture> texture_;
    sf::Sprite sprite_;

};

(两个构造函数都很基础,我没有在任何地方使用任何新的)

我有一个 unordered_map 的无序地图来存储我的块:

std::unordered_map<int, std::unordered_map<int, Block>> blocks_;

但是当我尝试删除一个时:

if(blocks_[i%lenght].find((i-i%lenght)/lenght) != blocks_[i%lenght].end())
    blocks_[i%lenght].erase((i-i%lenght)/lenght);

我得到了错误:

double free or corruption (out)

我试图打印析构函数,在我得到这个错误之前只调用了 Block 的析构函数。

找了2小时左右,终于在这里问了,谢谢!

【问题讨论】:

  • 您是否通过valgrind 运行您的代码或尝试在打开地址清理的情况下进行构建?
  • i-i%lenght)/lenght -- 我们不知道这相当于什么。为什么不在使用之前打印出这些值,以便知道它们是否有效?
  • 这只是在连续内存上恢复 Y 位置的计算,我打印它确实得到了好密钥,没问题。
  • 如果没有完整且可验证的示例,就不可能肯定地说,但是在您的 Block 构造函数中,您传递了一个 sf::Texture 原始指针,但在 Block 中,您有一个 std::shared_ptrsf::Texture。我猜你有多个不相关的共享指针指向同一个纹理。
  • 猜你必须使用你的调试器;这里没有足够的信息来猜测发生了什么 - 例如哪个对象被双重释放。

标签: c++ sfml double-free


【解决方案1】:

你的问题出在构造函数上:

Block::Block(sf::Texture *const &texture, sf::Vector2f const &pos,
             sf::IntRect const &textureRect, int const &type)
    : Case(pos, textureRect, 2), texture_(texture),
      sprite_(*texture_, textureRect_) {}

虽然这样写不一定是错的,但如果你将相同的纹理传递给多个块,那就错了:

sf::Texture *tex = new sf::Texture(/* ... params ... */);

Block b1(tex, /* ... remaining params ... */);
Block b2(tex, /* ... remaining params ... */);

现在两个独立的shared_ptr 认为他们是tex 的唯一所有者,因此一旦删除其中一个块,纹理也会被删除,所以如果两个块都被删除,那么你有一个双重免费.

到目前为止,这被认为是一种反模式。通常,如果您使用智能指针,那么您应该将原始指针视为不拥有指针,并且您永远不应该从不拥有指针构造智能指针。 (但是,此规则有一个例外,如果您使用不使用智能指针的库,那么您需要检查它们返回或接收的原始指针是否被视为欠指针,如果是,则可能是可以将该原始指针转换为智能指针。)

您的代码应该是这样的:

Block::Block(const std::shared_ptr<sf::Texture> &texture, sf::Vector2f const &pos,
             sf::IntRect const &textureRect, int const &type)
    : Case(pos, textureRect, 2), texture_(texture),
      sprite_(*texture_, textureRect_) {}

你应该像这样构造你的Blocks

std::shared_ptr<Texture> tex = std::make_shared<Texture>(/* ... params ... */);

Block b1(tex, /* ... remaining params ... */);
Block b2(tex, /* ... remaining params ... */);

这并不意味着你不应该使用原始指针。如果你例如会有一个函数可以将纹理绘制到屏幕上,那么原始指针就可以了:

void draw_texture( sf::Texture * const tex) {
   // do some drawing but don't store tex somewhere
   // so tex is only used within that draw_texture call
}

那么你会这样称呼它:

std::shared_ptr<Texture> tex = std::make_shared<Texture>(/* ... params ... */);

draw_texture(tex.get());

【讨论】:

  • draw_texture( sf::Texture * const tex) 是一种反模式,因为它让读者问:它是欠指针吗?谁负责释放它?绘图功能会将其存储在里面吗?最好在这种情况下传递引用,除非 nullptr 是有效参数之一
  • @MichaelVeksler is it an owing pointer? Who is responsible to free it? 不,这不是真的,如果您的项目/库使用智能指针,那么您应该明确并确保项目/库中的所有原始指针都不是拥有指针。是的,如果操作不会修改对象,您可以将其作为 const 引用传递,如果您需要在不是 const 的对象上调用函数,那么您可以将其作为 const 指针传递,而不是作为引用传递,因为引用可能会更改传入的对象。(作为非 const ref 传递被视为反模式)
  • @MichaelVeksler 如果 const 引用不起作用,并且您需要确保指针不为空,那么您将使用 not_null,如 C++ Core Guidelines 中所建议的那样。该类型的实现是例如可在Guidelines Support Library of Microsoft
  • 所以你基本上反对这样的答案:stackoverflow.com/a/7058373/4955498?原始指针比引用更危险,因为它们可能会被意外转换为智能指针
  • @MichaelVeksler 正如我所说,void foo(const Bar &amp; test) 非常好,将是我的首选,但只有在可以使用 const 时才有效。 void foo(Bar &amp; test) 有问题,如果我看void foo(Bar &amp; test),那么写foo(something) 不会改变something 并不是自我解释,所以你需要假设它可能会这样做,所以有人可能会不小心写test = Bar()在 foo 中覆盖test,并且该错误不如将指针指向智能指针的协调那么明显。
猜你喜欢
  • 2018-03-20
  • 1970-01-01
  • 1970-01-01
  • 2023-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多