首页 > 解决方案 > 防止或检测“this”在使用过程中被删除

问题描述

我经常看到的一个错误是容器在迭代时被清除。我试图整理一个小示例程序来演示这种情况。需要注意的一件事是,这通常会发生在许多函数调用深处,因此很难检测到。

注意:这个例子故意展示了一些设计不佳的代码。我正在尝试找到一种解决方案来检测由编写此类代码引起的错误,而无需仔细检查整个代码库(约 500 个 C++ 单元)

#include <iostream>
#include <string>
#include <vector>

class Bomb;

std::vector<Bomb> bombs;

class Bomb
{
  std::string name;

public:
  Bomb(std::string name)
  {
    this->name = name;
  }

  void touch()
  {
    if(rand() % 100 > 30)
    {
      /* Simulate everything being exploded! */
      bombs.clear();

      /* An error: "this" is no longer valid */
      std::cout << "Crickey! The bomb was set off by " << name << std::endl;
    }
  }
};

int main()
{
  bombs.push_back(Bomb("Freddy"));
  bombs.push_back(Bomb("Charlie"));
  bombs.push_back(Bomb("Teddy"));
  bombs.push_back(Bomb("Trudy"));

  for(size_t i = 0; i < bombs.size(); i++)
  {
    bombs.at(i).touch();
  }

  return 0;
}

任何人都可以提出一种保证这种情况不会发生的方法吗?我目前可以检测到这种事情的唯一方法是用 mmap / mprotect 替换全局newdelete空闲内存访问后检测使用。但是,如果向量不需要重新分配(即仅删除了一些元素或新大小还不是保留大小),这和 Valgrind 有时无法拾取它。理想情况下,我不想克隆大部分 STL 来制作一个 std::vector 版本,该版本总是在调试/测试期间重新分配每个插入/删除。

一种几乎可行的方法是,如果std::vector改为包含std::weak_ptr,则使用.lock()创建临时引用可防止在 classes 方法内执行时将其删除。但是,这不能与std::shared_ptr一起使用,因为您不需要lock()并且与普通对象相同。仅仅为此创建一个弱指针容器是很浪费的。

任何人都可以想出一种方法来保护自己免受这种伤害。

标签: c++memorystlcontainersunsafe

解决方案


最简单的方法是使用链接的Clang MemorySanitizer运行单元测试。让一些持续集成的 Linux 机器在每次推送到 repo 时自动执行它。

MemorySanitizer 具有“破坏后使用检测”(标志-fsanitize-memory-use-after-dtor+ 环境变量MSAN_OPTIONS=poison_in_dtor=1),因此它会炸毁执行代码的测试,并使您的连续集成变为红色。

如果您既没有单元测试也没有持续集成,那么您也可以使用 MemorySanitizer 手动调试代码,但与最简单的方法相比,这很难。所以最好开始使用持续集成和编写单元测试。

请注意,在运行析构函数但内存尚未释放后,可能存在内存读取和写入的正当原因。例如 std::variant<std::string,double>. 它让我们可以分配它,std::string因此double它的实现可能会破坏string并重用相同的存储空间double。不幸的是,目前过滤此类情况是手动工作,但工具会不断发展。


推荐阅读