MinGW下C++17 thread_local变量析构时值异常问题
C++ thread_local对象析构时成员值异常问题
我基于C++17标准编写了如下代码:
#include <mutex> #include <iostream> #include <thread> #include <vector> std::mutex mutex{}; struct Test { ~Test() { { std::lock_guard lock{mutex}; std::cout << "~Test() = " << a << std::endl; } delete a; } int *a; }; thread_local Test test{}; void Foo(int i) { test.a = new int{i}; std::lock_guard lock{mutex}; std::cout << "Thread: " << std::this_thread::get_id() << " value: " << test.a << std::endl; } int main() { std::vector<std::thread> threads; threads.reserve(7); for (std::size_t i{1}; i < 8; ++i) { threads.emplace_back(Foo, i); } Foo(0); for (auto &thread : threads) { thread.join(); } threads.clear(); return 0; }
代码逻辑为创建多个线程,为thread_local修饰的Test类型线程局部变量test的int*类型成员a分配堆内存,在Test::~Test()中编写了内存释放逻辑避免内存泄漏,但程序运行出现非预期结果:线程执行thread_local对象的析构函数时,成员指针a的取值与Foo()函数中内存分配返回的地址不一致,最终触发运行错误。
Debug模式下程序的可能输出如下:
Thread: 4 value: 0x14ca4b81ae0 Thread: 2 value: 0x14ca4b84f80 ~Test() thread: 4 value: 0x211bbc59c792f Thread: 3 value: 0x14ca4b847a0 Signal: SIGTRAP (Trace/breakpoint trap) Signal: ? (Unknown signal)
非Debug模式下程序的可能输出如下:
Thread: 2 value: 0x224eb8926c0 Thread: 3 value: 0x224eb892820 ~Test() thread: 2 value: 0x224eb8926c0 Thread: 1 value: 0x224eba77670 ~Test() thread: 3 value: 0x224eb892820 Thread: 6 value: 0x224eba77650 ~Test() thread: 6 value: 0x224eba77650 Thread: 5 value: 0x224eba776a0 ~Test() thread: 5 value: 0x224eba776a0 Thread: 4 value: 0x224eba77680 ~Test() thread: 4 value: 0x224eba77680 Thread: 7 value: 0x224eba77700 ~Test() thread: 7 value: 0x224eba77700 Thread: 8 value: 0x224eba77690 ~Test() thread: 8 value: 0x224eba77690 ~Test() thread: 1 value: 0x224eba77670 Process finished with exit code 0
更新说明
该问题在使用MinGW-w64 9.0、g++、CMake构建(包含Release、Debug两种构建类型)时稳定复现,但使用x86架构MSVC编译时程序运行完全正常。
问题原因
这个异常不是代码逻辑的写法错误,是旧版MinGW-w64的TLS实现bug导致的:
- MinGW-w64 9.x对应的GCC版本低于11,在Windows平台默认使用emutls(模拟线程局部存储)方案替代系统原生TLS,该方案对带非平凡析构函数的全局
thread_local对象的清理顺序存在缺陷:子线程退出时,TLS存储内存块会先被回收,之后才会调用thread_local对象的析构函数,此时访问对象成员a读到的是已释放内存中的随机垃圾值,最终delete野指针触发崩溃。 - Debug模式下编译器不会做寄存器缓存优化,析构函数访问
a时会直接读取已经被回收的内存,因此稳定复现野值问题;Release模式下编译器会把Foo函数中赋值的a地址临时缓存到寄存器,部分场景下析构时直接使用寄存器中的缓存值,因此看起来运行正常,但本质是未定义行为。 - MSVC编译时直接调用Windows原生TLS接口,不存在析构顺序和内存回收的冲突问题,因此运行完全正常。
修复方法
- 推荐方案:升级MinGW-w64到GCC 11及以上版本,新版本已经修复了emutls的析构顺序相关bug。
- 兼容旧版本MinGW的方案:不要依赖全局
thread_local对象的自动析构逻辑,手动控制对象生命周期,比如将test定义为线程局部指针,在线程入口函数开头手动构造对象,线程退出前手动调用析构释放内存,避开自动析构时的TLS内存回收问题。 - 编码规范优化:给
Test添加显式默认构造函数,将a成员初始化为nullptr,不要依赖聚合初始化的零初始化规则,避免其他场景下出现未初始化指针问题:
struct Test { Test() : a(nullptr) {} ~Test() { { std::lock_guard lock{mutex}; std::cout << "~Test() = " << a << std::endl; } delete a; } int *a; };
内容的提问来源于stack exchange,提问作者Denis319199
相关产品推荐
相关产品推荐

