c++ - 清理拥有的 (!) 字符串成员时析构函数偶尔崩溃

标签 c++ c++11 memory-management lldb

我正试图在这个简单的 C++ 类的析构函数中追踪偶尔会导致我的应用程序崩溃的错误:

class CrashClass {

public:
         CrashClass(double r1, double s1, double r2, double s2, double r3, double s3, string dateTime) : mR1(r1), mS1(s1), mR2(r2), mS2(s2), mR3(r3), mS3(s3), mDateTime(dateTime) { }
         CrashClass() : mR1(0), mS1(0), mR2(0), mS2(0), mR3(0), mS3(0) { }
        ~CrashClass() {}

    string  GetDateTime()   { return mDateTime; }

private:
    double mR1, mS1, mR2, mS2, mR3, mS3;
    string mDateTime;
};

这些对象中的一堆被卡在标准的 C++ vector 中并在第二个类中使用:

class MyClass {
    (...)

private:
    vector<CrashClass>    mCrashClassVec;
};

MyClass 根据需要多次创建和释放。

代码在 macOS 10.14.4 下的最新 Xcode 10.1 上使用 C++17。

所有这些都是运行数小时甚至数天的计算密集型模拟应用程序的一部分。在并行运行 12 个计算(使用 macOS 的 GCD 框架)的 6 核 i7 机器上,这经常会在几个小时后崩溃,并显示

未分配正在释放的指针

MyClass 中的成员上调用 mCrashClassVec.clear() 时出错,即

frame #0: 0x00007fff769a72f6 libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00000001004aa80d libsystem_pthread.dylib`pthread_kill + 284
frame #2: 0x00007fff769116a6 libsystem_c.dylib`abort + 127
frame #3: 0x00007fff76a1f977 libsystem_malloc.dylib`malloc_vreport + 545
frame #4: 0x00007fff76a1f738 libsystem_malloc.dylib`malloc_report + 151
frame #5: 0x0000000100069448 BackTester`MyClass::DoStuff(int, int) [inlined] std::__1::__libcpp_deallocate(__ptr=<unavailable>) at new:236 [opt]
frame #6: 0x0000000100069443 BackTester`MyClass::DoStuff(int, int) [inlined] std::__1::allocator<char>::deallocate(__p=<unavailable>) at memory:1796 [opt]
frame #7: 0x0000000100069443 BackTester`MyClass::DoStuff(int, int) [inlined] std::__1::allocator_traits<std::__1::allocator<char> >::deallocate(__p=<unavailable>) at memory:1555 [opt]
frame #8: 0x0000000100069443 BackTester`MyClass::DoStuff(int, int) [inlined] std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string() at string:1941 [opt]
frame #9: 0x0000000100069439 BackTester`MyClass::DoStuff(int, int) [inlined] std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string() at string:1936 [opt]
frame #10: 0x0000000100069439 BackTester`MyClass::DoStuff(int, int) [inlined] CrashClass::~CrashClass(this=<unavailable>) at CrashClass.h:61 [opt]
frame #11: 0x0000000100069439 BackTester`MyClass::DoStuff(int, int) [inlined] CrashClass::~CrashClass(this=<unavailable>) at CrashClass.h:61 [opt]
frame #12: 0x0000000100069439 BackTester`MyClass::DoStuff(int, int) [inlined] std::__1::allocator<CrashClass>::destroy(this=<unavailable>, __p=<unavailable>) at memory:1860 [opt]
frame #13: 0x0000000100069439 BackTester`MyClass::DoStuff(int, int) [inlined] void std::__1::allocator_traits<std::__1::allocator<CrashClass> >::__destroy<CrashClass>(__a=<unavailable>, __p=<unavailable>) at memory:1727 [opt]
frame #14: 0x0000000100069439 BackTester`MyClass::DoStuff(int, int) [inlined] void std::__1::allocator_traits<std::__1::allocator<CrashClass> >::destroy<CrashClass>(__a=<unavailable>, __p=<unavailable>) at memory:1595 [opt]
frame #15: 0x0000000100069439 BackTester`MyClass::DoStuff(int, int) [inlined] std::__1::__vector_base<CrashClass, std::__1::allocator<CrashClass> >::__destruct_at_end(this=<unavailable>, __new_last=0x00000001011ad000) at vector:413 [opt]
frame #16: 0x0000000100069429 BackTester`MyClass::DoStuff(int, int) [inlined] std::__1::__vector_base<CrashClass, std::__1::allocator<CrashClass> >::clear(this=<unavailable>) at vector:356 [opt]
frame #17: 0x0000000100069422 BackTester`MyClass::DoStuff(int, int) [inlined] std::__1::vector<CrashClass, std::__1::allocator<CrashClass> >::clear(this=<unavailable>) at vector:749 [opt]

旁注:被清除的 vector 可能还没有任何元素。

在堆栈跟踪 (bt all) 中,我可以看到其他线程在它们的 CrashClass vector 拷贝上执行操作,但据我所知,比较堆栈中的地址trace 所有这些实际上都是私有(private)拷贝(按照设计),即这些数据都不会在线程之间共享。

自然地这个错误只发生在完整的生产模式下,即所有重现崩溃的尝试

  • DEBUG模式运行,
  • 在 Lldb(Xcode)的 Address Sanitizer 下运行(许多小时/整夜),
  • 在 Lldb (Xcode) 的 Thread Sanitizer 下运行(很多小时/整夜),
  • 运行类的简化版本,只保留/复制关键代码,

失败并且没有触发崩溃。

为什么解除分配分配在堆栈上的简单成员会失败并出现未分配释放指针错误?

也非常欢迎提供有关如何调试此问题或以更稳健的方式触发错误以进一步调查的其他提示。

2019 年 5 月更新

该错误仍然存​​在,导致应用程序间歇性崩溃,我开始相信我遇到的问题实际上是由英特尔最近 CPU 型号中的数据损坏错误引起的。

https://mjtsai.com/blog/2019/05/17/microarchitectural-data-sampling-mds-mitigation/

https://mjtsai.com/blog/2017/06/27/bug-in-skylake-and-kaby-lake-hyper-threading/

https://www.tomshardware.com/news/hyperthreading-kaby-lake-skylake-skylake-x,34876.html

最佳答案

您可以尝试一些技巧:

  • 使用单线程运行生产版本更长时间(比如一周或两周),看看它是否崩溃。
  • 考虑到您可能存在内存碎片这一事实,请确保您没有消耗所有可用的 RAM。
  • 确保您的程序没有内存泄漏或运行时间越长内存使用量增加。
  • 通过添加额外值来添加一些跟踪,将值设置为析构函数中已知的值(这样,如果执行两次删除,您就会识别出该模式)。
  • 尝试在其他平台和编译器下运行程序。
  • 您的编译器或库可能包含错误。尝试另一个(更新的)版本。
  • 从原始版本中删除代码,直到它不再崩溃。如果您能够始终如一地使用以某种方式破坏内存的序列进行崩溃,那么效果会更好。
  • 一旦发生崩溃,使用完全相同的数据(对于每个线程)运行程序,看看它是否总是在同一位置崩溃。
  • 重写或验证应用程序中的任何不安全代码。避免转换、printf 和其他老派变量参数函数以及任何不安全的 strcpy 和类似函数。
  • 使用经过检查的 STL 版本。
  • 尝试未优化的发布版本。
  • 尝试优化调试版本。
  • 了解编译器的 DEBUG 和 RELEASE 版本之间的差异。
  • 从零开始重写有问题的代码。也许它不会有错误。
  • 在数据崩溃时检查数据。
  • 检查您的错误/异常处理,看看您是否忽略了一些潜在的问题。
  • 测试程序在内存不足、磁盘空间不足、抛出异常时的行为......
  • 确保您的调试器在处理或未处理每个抛出的异常时停止。
  • 确保您的程序在没有警告的情况下编译和运行,或者您了解它们并确定这无关紧要。
  • 在崩溃时检查数据,看是否正常。
  • 您可以保留内存以减少碎片和重新分配。如果您的程序运行数小时,内存碎片可能过多,系统无法找到足够大的 block 。
  • 由于您的程序是多线程的,因此请确保您的运行时也与之兼容。
  • 确保您不会跨线程共享数据或它们受到充分保护。

关于c++ - 清理拥有的 (!) 字符串成员时析构函数偶尔崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54388327/

相关文章:

c++ - Visual Studio 2015 中的编译器是什么

c++ - 迭代器和指针有什么关系?

c++ - 在没有 reinterpret_cast 的情况下将 unsigned char 转换为 std::string 的方法?

c++ - 将对象重置为初始状态的模式

c++ - 在 2D vector 中移动行

ios - Swift 管理内存

c++ - 拉普拉斯过滤器非常暗。几乎不起作用

C++ 指针值随 static_cast 改变

winforms - 拖放如何影响 Windows 窗体应用程序中的内存使用?

c - .exe 中嵌入的二进制资源以及加载时的内存管理