从向量中移除项目时出现Valgrind错误

时间:2016-10-26 07:48:26

标签: c++ c++11

对于大多数人来说,这可能看似重复。但是我花了很多时间找到解决方案。实现了stackoverflow和其他编码站点中给出的许多解决方案。最后我设法修复了它,但我仍然不知道我的旧实现有什么问题。

请帮我查看导致查看旧代码,新代码,单元测试和valgrind错误的确切错误。

注意:

  • 我正在测试单元测试中的代码(Google测试框架)。
  • 使用C ++ 11编译
  • m_queue_是一个std :: vector
  • 使用过的Google C ++编码标准

测试

  • 队列有2个SAPA项目(由新运营商创建)
  • 按ID删除第一个项目(队列现在只有一个)
  • 删除唯一的项目 留下了它的身份
  • 第二次删除似乎在访问项目的m_id_时给出了valgrind错误

这是我的队列项基类

class Item {
 public:
  Item() {
    type = Type::kInvalid;
  }

  virtual ~Item() {}

  Type type;
  string m_id_ = string("");
};

这是儿童类

class SAPA : public Item {
 public:
  SAPA() { Item::type = Type::kSAPA; }
  ~SAPA() {}
};

用于在符合特定条件的情况下删除项目的旧代码(RemoveIf)。导致VALGRIND问题。

This was proposed as a correct way to remove items from a vector in many posts.

void Queue::RemoveItems(const string& id) const {
  vector<Item*>::iterator it = m_queue_.begin();
  while (it != m_queue_.end()) {
    Item* item = *it;
    if (item == nullptr) {
      continue;
    }

    if (RemoveIf(item, id)) {
      delete item;
      item = nullptr;
      it = m_queue_.erase(it);
    } else {
      ++it;
    }
  }
}

RemoveIf功能

bool Queue::RemoveIf(Item* item,
                     const string& id) const {
**cout << id.c_str() << endl; <--- seems to cause the invalid read**

  if (item->m_id_.compare(id) == 0) {
    return true;
  }

  return false;
}

VALGRIND输出显示大小为8的无效读取。抱歉,这包含一些项目特定名称。

> ==21919== Invalid read of size 8
> ==21919==    at 0x5880B90: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::c_str() const (in
> /usr/lib64/libstdc++.so.6.0.21) 
> ==21919==    by 0xEC416C: Queue::RemoveIf(network::multiplexer::Item*, blf::String const&) const (network_multiplexer_queue.cc:99)
> ==21919==    by 0xEC3FFB: Queue::RemoveItems(blf::String const&) const (network_multiplexer_queue.cc:85)
> ==21919==    by 0xEC4FDC: Queue::OnTimer() const (network_multiplexer_queue.cc:228)
> ==21919==    by 0xFB05E0: (anonymous namespace)::NetworkMultiplexerTest_sapaTimeout_shouldBeHandled_successfully_Test::TestBody()
> (network_multiplexer_comm_test.cc:1201)
> ==21919==    by 0x1232186: void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test,
> void>(testing::Test*, void (testing::Test::*)(), char const*) (in
> /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x122C547: void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test,
> void>(testing::Test*, void (testing::Test::*)(), char const*) (in
> /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x12124B7: testing::Test::Run() (in /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x1212D99: testing::TestInfo::Run() (in /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x1213444: testing::TestCase::Run() (in /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x1219F2E: testing::internal::UnitTestImpl::RunAllTests() (in
> /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x1233583: bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl,
> bool>(testing::internal::UnitTestImpl*, bool
> (testing::internal::UnitTestImpl::*)(), char const*) (in
> /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==  Address 0x6d24a00 is 16 bytes inside a block of size 112 free'd
> ==21919==    at 0x4C2A131: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
> ==21919==    by 0xED3991: SAPA::~SAPA() (network_multiplexer_queue_item.h:82)
> ==21919==    by 0xEC4045: Queue::RemoveItems(blf::String const&) const (network_multiplexer_queue.cc:86)
> ==21919==    by 0xEC4FDC: OnTimer() const (network_multiplexer_queue.cc:228)
> ==21919==    by 0xFB05E0: (anonymous namespace)::NetworkMultiplexerTest_sapaTimeout_shouldBeHandled_successfully_Test::TestBody()
> (network_multiplexer_comm_test.cc:1201)
> ==21919==    by 0x1232186: void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test,
> void>(testing::Test*, void (testing::Test::*)(), char const*) (in
> /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x122C547: void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test,
> void>(testing::Test*, void (testing::Test::*)(), char const*) (in
> /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x12124B7: testing::Test::Run() (in /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x1212D99: testing::TestInfo::Run() (in /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x1213444: testing::TestCase::Run() (in /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x1219F2E: testing::internal::UnitTestImpl::RunAllTests() (in
> /home/sajith/cioffi/cioffi-linux/build/unit_tests)
> ==21919==    by 0x1233583: bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl,
> bool>(testing::internal::UnitTestImpl*, bool
> (testing::internal::UnitTestImpl::*)(), char const*) (in
> /home/sajith/cioffi/cioffi-linux/build/unit_tests)

下面修复了valgrind问题 这是向后迭代并删除项目的新代码。

auto it = m_queue_.end();
  while (it > m_queue_.begin()) {
    it--;
    Item* item = *it;
    if (item == nullptr) {
      continue;
    }

    if (RemoveIf(item, id)) {
      delete item;
      item = nullptr;
      it = m_queue_.erase(it);
    }
  }

1 个答案:

答案 0 :(得分:3)

修改

在澄清之后,实际原因似乎非常清楚。

你正在过去&#34; id&#34;作为const string&传递对原始对象的引用而不是副本。由于它是来自第一个对象,我猜测你上面的某个地方有const string& id = *m_queue_.begin()->m_id_;,然后你继续将它传递给RemoveIf。因为比较可以保证删除第一个项目,所以在循环中会发生这种情况。现在id是对该项目中数据的悬空引用。它在反向迭代版本中工作的原因是第一个项目成为您删除的最后一个项目。删除后,id没有其他任何内容。您可以更改将id指定为string id = *m_queue_.begin()->m_id_;的代码行。通过在此时使其成为string而非string&,您可以强制制作副本。该副本将有一生到期末。

结束编辑

您应该关注的是std库函数。特别是,您需要vector::erase()std::remove_if()。你想要的擦除版本是一个带有一对迭代器而不是一次擦除它的版本。当你调用它时,它看起来像m_queue_.erase(XXXX, m_queue_.end())。现在什么是XXXX - 最终成为remove_if的返回值。 remove_if的参数是一对迭代器和一元谓词。 (即,对于向量中的任何内容采用const T&的函数,如果应该删除它,则返回true。)返回值是一个迭代器,刚好超过未删除项的末尾。

在c ++ 11中,这可能看起来像:

string id = "the_id_to_filter";
m_queue_.erase(std::remove_if(m_queue_.begin(), m_queue_.end(), 
                              [&id](const Item* item) { 
                                  return item_.m_id_ == id;
                              });

在pre-c ++ 11中,你可以用以下代码替换lambda:

struct Filter {
   Filter(const string& id) : id_(id) {}
   string id_;
   bool operator()(const Item* item) { return item.m_id_ == id_; }
};

然后你的电话会是这样的:

string id = "the_id_to_filter";
m_queue_.erase(std::remove_if(m_queue_.begin(), m_queue_.end(), Filter(id)));

如果您的向量包含nullptr或其他您不应解除引用的值,则有效,请在谓词函数中添加这些检查。此外,如果向量拥有这些项目,您可能希望将其设为vector<std::unique_ptr<Item>>而不是vector<Item*>,如果您不使用,则擦除(remove_if)惯用法可能会泄漏。它还使您无需记住删除内容。

使用库函数可以避免因循环中的一个错误和各种其他痛苦而感到沮丧。

供参考: