我经常看到的一个错误是容器在迭代时被清除。我试图编写一个小示例程序来演示这种情况的发生。需要注意的一件事是,这种情况经常会发生在许多深度函数调用中,因此很难检测到。
注意:这个例子故意展示了一些设计不佳的代码。我正在尝试找到一种解决方案来检测因编写此类代码而引起的错误,而无需仔细检查整个代码库(约 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;
}
任何人都可以提出一种保证这种情况不会发生的方法吗? 我目前检测到这种情况的唯一方法是将全局 new 和 delete 替换为 mmap/mprotect并在空闲内存访问后检测使用情况。然而,如果 vector 不需要重新分配(即仅删除一些元素或新大小尚未保留大小),则此方法和 Valgrind 有时无法拾取它。理想情况下,我不想克隆大部分 STL 来制作一个在调试/测试期间始终重新分配每个插入/删除的 std::vector 版本。
一种几乎可行的方法是,如果 std::vector 包含 std::weak_ptr,则使用 .lock()> 创建临时引用可防止其在类方法内执行时被删除。但是,这不能与 std::shared_ptr 一起使用,因为您不需要 lock() ,并且对于普通对象也是如此。仅仅为此创建一个弱指针容器是很浪费的。
其他人能想出一种方法来保护我们自己免受这种情况的影响吗?
最佳答案
最简单的方法是使用 Clang MemorySanitizer 运行单元测试链接到. 让一些持续集成的 Linux 盒子在每次推送时自动执行此操作 进入仓库。
MemorySanitizer 具有“销毁后使用检测”(标志 -fsanitize-memory-use-after-dtor
+ 环境变量 MSAN_OPTIONS=poison_in_dtor=1
),因此它会破坏执行代码的测试并使您的持续集成变成红色。
如果您既没有单元测试也没有持续集成,那么您也可以使用 MemorySanitizer 手动调试代码,但这与最简单的方法相比是困难的。因此最好开始使用持续集成并编写单元测试。
请注意,析构函数运行后可能存在内存读写但内存尚未释放的合理原因。例如std::variant<std::string,double>
。它让我们可以分配它 std::string
然后double
因此它的实现可能会破坏 string
并重复使用相同的存储 double
。不幸的是,目前过滤掉此类情况是手动工作,但工具正在不断发展。
关于c++ - 防止或检测 "this"在使用过程中被删除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50838624/