c++ - `std::call_once` 在 Windows 上的 Clang 12 上总是出现段错误(使用 libstdc++ 时)

标签 c++ clang

我正在寻找一种解决方法,这可能涉及修补 libstdc++ header 。保留二进制兼容性是首选但不是强制性的,因为我没有使用除 libstdc++ 之外的任何预编译 C++ 代码。
我想保留 std::call_once接口(interface),因为我正在尝试编译使用 is 的第三方代码,我不想更改它。

这是我的代码:

#include <iostream>
#include <mutex>

int main()
{
    std::once_flag flag;
    std::call_once(flag, []{std::cout << "Once!\n";});
}
运行它会导致段错误。
我使用 Clang 12 使用 MSYS2 GCC 10.2 中的标准库将它从 Ubuntu 交叉编译到 Windows。然后我用 Wine 测试结果(快速测试表明它也在 VM 上崩溃)。但是您应该能够通过在 Windows 上本地编译来重现结果(使用官方 Clang 二进制文件 + MSYS2 GCC,因为 MSYS2 还没有 Clang 12)。
我这样编译它:
clang++-12 1.cpp --target=x86_64-w64-mingw32 --sysroot=/mingw64 -pthread -femulated-tls
如果我添加 -g , GDB 显示如下:
Program received signal SIGSEGV, Segmentation fault.
0x00000001e014dc4a in ?? () from Z:\home\holyblackcat\Sandbox\2\libgcc_s_seh-1.dll
(gdb) bt
#0  0x00000001e014dc4a in ?? () from Z:\home\holyblackcat\Sandbox\2\libgcc_s_seh-1.dll
#1  0x00000000004015f3 in std::call_once<main::$_0> (__once=..., __f=...) at /mingw64/include/c++/10.2.0/mutex:721
#2  0x00000000004015b5 in main () at 1.cpp:8
(gdb) f 1
#1  0x00000000004015f3 in std::call_once<main::$_0> (__once=..., __f=...) at /mingw64/include/c++/10.2.0/mutex:721
721           __once_callable = std::__addressof(__callable);
(gdb) list
716           auto __callable = [&] {
717               std::__invoke(std::forward<_Callable>(__f),
718                             std::forward<_Args>(__args)...);
719           };
720     #ifdef _GLIBCXX_HAVE_TLS
721           __once_callable = std::__addressof(__callable);
722           __once_call = []{ (*(decltype(__callable)*)__once_callable)(); };
723     #else
724           unique_lock<mutex> __functor_lock(__get_once_mutex());
725           __once_functor = __callable;
Clang 版本是:
# clang++-12 --version --target=x86_64-w64-mingw32 --sysroot=/mingw64 -pthread -femulated-tls
Ubuntu clang version 12.0.1-++20210423082613+072c90a863aa-1~exp1~20210423063319.76
Target: x86_64-w64-windows-gnu
Thread model: posix
GCC 版本(提供 libstdc++)是:
# g++ --version
g++.exe (Rev10, Built by MSYS2 project) 10.2.0
用这个 GCC 编译代码(它是 native 的,不是交叉编译的),会产生一个工作代码。
这里发生了什么?是否有任何解决方法,或者我必须降级到 Clang 11?

我举报了Clang bug .
This bug看起来相关。

这是 call_once 的当前段错误实现, 预处理后:
struct once_flag
{
  private:
    typedef __gthread_once_t __native_type;
    __native_type _M_once = 0;

  public:
    constexpr once_flag() noexcept = default;
    once_flag(const once_flag &) = delete;
    once_flag &operator=(const once_flag &) = delete;
    template <typename _Callable, typename... _Args>
    friend void call_once(once_flag &__once, _Callable &&__f, _Args &&...__args);
};

extern __thread void *__once_callable;
extern __thread void (*__once_call)();
extern "C" void __once_proxy(void);

template <typename _Callable, typename... _Args>
void call_once(once_flag &__once, _Callable &&__f, _Args &&...__args)
{
    auto __callable = [&]
    {
        std::__invoke(std::forward<_Callable>(__f), std::forward<_Args>(__args)...);
    };
    __once_callable = std::__addressof(__callable);
    __once_call = []{(*(decltype(__callable) *)__once_callable)();};
    int __e = __gthread_once(&__once._M_once, &__once_proxy);
    if (__e)
        __throw_system_error(__e);
}

最佳答案

这已在 Clang 13 中修复,位于 commit 0e4cf80 . (感谢 @mstorsjo。)

如果您被 Clang 12 卡住,您可以修补 libstdc++ header 作为解决方法。下面的补丁将全局线程局部变量替换为 static函数局部线程局部变量,不受 bug 影响。
要申请,请将以下内容保存到 patch.txt ,然后执行 patch /mingw64/include/c++/10.3.0/mutex patch.txt .

@@ -691,8 +691,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION

   /// @cond undocumented
 #ifdef _GLIBCXX_HAVE_TLS
-  extern __thread void* __once_callable;
-  extern __thread void (*__once_call)();
+  inline void *&__once_callable_get() {static __thread void *__ret; return __ret;}
+  inline void (*&__once_call_get())() {static __thread void (*__ret)(); return __ret;}
 #else
   extern function<void()> __once_functor;

@@ -703,7 +703,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   __get_once_mutex();
 #endif

-  extern "C" void __once_proxy(void);
+  extern "C" inline void __once_proxy_inline(void) {__once_call_get()();}
   /// @endcond

   /// Invoke a callable and synchronize with other calls using the same flag
@@ -718,15 +718,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            std::forward<_Args>(__args)...);
       };
 #ifdef _GLIBCXX_HAVE_TLS
-      __once_callable = std::__addressof(__callable); // NOLINT: PR 82481
-      __once_call = []{ (*(decltype(__callable)*)__once_callable)(); };
+      __once_callable_get() = std::__addressof(__callable); // NOLINT: PR 82481
+      __once_call_get() = []{ (*(decltype(__callable)*)__once_callable_get())(); };
 #else
       unique_lock<mutex> __functor_lock(__get_once_mutex());
       __once_functor = __callable;
       __set_once_functor_lock_ptr(&__functor_lock);
 #endif

-      int __e = __gthread_once(&__once._M_once, &__once_proxy);
+      int __e = __gthread_once(&__once._M_once, &__once_proxy_inline);

 #ifndef _GLIBCXX_HAVE_TLS
       if (__functor_lock)
这是 GCC 10.2 的等效补丁(以上是 10.3):
@@ -691,8 +691,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   /// @cond undocumented
 #ifdef _GLIBCXX_HAVE_TLS
-  extern __thread void* __once_callable;
-  extern __thread void (*__once_call)();
+  inline void *&__once_callable_get() {static __thread void *__ret; return __ret;}
+  inline void (*&__once_call_get())() {static __thread void (*__ret)(); return __ret;}
 #else
   extern function<void()> __once_functor;
 
@@ -703,7 +703,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   __get_once_mutex();
 #endif
 
-  extern "C" void __once_proxy(void);
+  extern "C" inline void __once_proxy_inline(void) {__once_call_get()();}
   /// @endcond
 
   /// Invoke a callable and synchronize with other calls using the same flag
@@ -718,15 +718,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            std::forward<_Args>(__args)...);
       };
 #ifdef _GLIBCXX_HAVE_TLS
-      __once_callable = std::__addressof(__callable);
-      __once_call = []{ (*(decltype(__callable)*)__once_callable)(); };
+      __once_callable_get() = std::__addressof(__callable);
+      __once_call_get() = []{ (*(decltype(__callable)*)__once_callable_get())(); };
 #else
       unique_lock<mutex> __functor_lock(__get_once_mutex());
       __once_functor = __callable;
       __set_once_functor_lock_ptr(&__functor_lock);
 #endif
 
-      int __e = __gthread_once(&__once._M_once, &__once_proxy);
+      int __e = __gthread_once(&__once._M_once, &__once_proxy_inline);
 
 #ifndef _GLIBCXX_HAVE_TLS
       if (__functor_lock)
@@ -735,8 +735,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
 #ifdef __clang_analyzer__
       // PR libstdc++/82481
-      __once_callable = nullptr;
-      __once_call = nullptr;
+      __once_callable_get() = nullptr;
+      __once_call_get() = nullptr;
 #endif
 
       if (__e)

关于c++ - `std::call_once` 在 Windows 上的 Clang 12 上总是出现段错误(使用 libstdc++ 时),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67262415/

相关文章:

c++ - LPD3DXFONT 不绘制

c++ - 使用ffmpeg example remuxing.c 录制rtmp 直播流到mp4 文件,它添加一个空的elst 框,如何禁止它?

c++ - 使用嵌入式硬件进行测试自动化

c++ - constexpr 可以和 volatile 结合使用吗?

c - 在哪里可以找到有关此 .s 文件中的代码的文档?

c++ - 如何让 Clang 忽略特定 block 中的特定警告?

c++ - 在 C++ 中创建多个动态分配的对象

c++ - 寻找更紧凑的语法(简单代码)——C++

c++ - Clang、std::shared_ptr 和 std::less/operator<

c - 有符号位域的多重不一致行为