我有一个程序,它使用
thread_local std::shared_ptr
来管理一些主要在本地线程上访问的对象。然而,当线程被加入并且线程本地的shared_ptr
正在销毁时,如果该程序是由MinGW(Windows 10)编译的,则调试时总会出现SIGSEGV。以下是最小的重现错误的代码:// main.cpp
#include <memory>
#include <thread>
void f() {
thread_local std::shared_ptr<int> ptr = std::make_shared<int>(0);
}
int main() {
std::thread th(f);
th.join();
return 0;
}
如何编译:
g++ main.cpp -o build\main.exe -std=c++17
编译器版本:
>g++ --version
g++ (x86_64-posix-seh-rev2, Built by MinGW-W64 project) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
使用gdb运行时,当主线程等待join()
时,它会在新线程中产生SIGSEGV。在gcc、clang(Linux)和MSVC(Windows)编译时,它能正常工作。
我尝试进行调试并发现,在调用RtlpWow64SetContextOnAmd64
时,一个连续的内存段包含线程本地的shared_ptr被擦除为重复的0xfeeefeee
,然后才被销毁。帧:
RtlpWow64SetContextOnAmd64 0x00007ffd8f4deb5f
RtlpWow64SetContextOnAmd64 0x00007ffd8f4de978
SbSelectProcedure 0x00007ffd8f4ae2e0
CloseHandle 0x00007ffd8ce3655b
pthread_create_wrapper 0x00007ffd73934bac
_beginthreadex 0x00007ffd8e9baf5a
_endthreadex 0x00007ffd8e9bb02c
BaseThreadInitThunk 0x00007ffd8ec87614
RtlUserThreadStart 0x00007ffd8f4c26a1
汇编语言:
...
mov %rax,(%rdi)
movdqu %xmm0,(%rsi) ; <------ erased here
call 0x7ffd8f491920 ; <ntdll!RtlReleaseSRWLockShared>
mov $0x1,%r9d
mov 0x30(%rsp),%rbx
...
后来,当shared_ptr被销毁时,在读取0xfeeefeee
时会出现SIGSEGV。
我想知道:
- 为什么MinGW(或Windows库?)在销毁之前擦除线程本地存储?在我看来,擦除内存应该只发生在销毁之后。我注意到,如果将
join()
替换为detach()
,程序正常退出。也许join()
对新线程有指示以擦除存储的操作? - 这种行为是否违反标准?我认为标准应禁止在销毁之前擦除内存。如果我错了,请指正。
thread_local
和本地变量混合使用是不寻常的用例。我猜想ptr
在f
结束时被销毁。建议将该变量移至全局作用域。另一方面,这意味着一个静态局部变量:https://dev59.com/JGEh5IYBdhLWcg3wHARD#22794640 - 273KThread 5 received signal SIGSEGV, Segmentation fault. [Switching to Thread 15196.0x27dc] 0x00007ff7e54133f4 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() () (gdb) x/i $rbp 0xfb957ff4b0: loopne 0xfb957ff4a6
。 - Eritque arcus