Bee*_*ope 6 c++ global-variables clang static-initialization c++17
inline考虑使用C++ 17 中的新变量功能声明的全局(命名空间范围)变量:
struct something {\n something();\n ~something();\n};\n\ninline something global;\nRun Code Online (Sandbox Code Playgroud)\n在 x86 上的 Clang 14 中,生成的用于在启动时初始化变量的程序集如下:
\n__cxx_global_var_init: # @__cxx_global_var_init\n push rbx\n mov al, byte ptr [rip + guard variable for global]\n test al, al\n je .LBB0_1\n.LBB0_4:\n pop rbx\n ret\n.LBB0_1:\n mov edi, offset guard variable for global\n call __cxa_guard_acquire\n test eax, eax\n je .LBB0_4\n mov edi, offset global\n call something::something() [complete object constructor]\n mov edi, offset something::~something() [complete object destructor]\n mov esi, offset global\n mov edx, offset __dso_handle\n call __cxa_atexit\n mov edi, offset guard variable for global\n pop rbx\n jmp __cxa_guard_release # TAILCALL\n mov rbx, rax\n mov edi, offset guard variable for global\n call __cxa_guard_abort\n mov rdi, rbx\n call _Unwind_Resume@PLT\nglobal:\n .zero 1\n\nguard variable for global:\n .quad 0 # 0x0\nRun Code Online (Sandbox Code Playgroud)\n这是一个双重检查锁定模式,它会导致线程安全的初始化过程:第一个进行test al, al初始乐观检查以查看变量是否已初始化,如果表明变量尚未初始化,则执行 \xe2\x80\x93 。未初始化 \xe2\x80\x93 进行调用,该调用__cxa_guard_acquire将再次检查锁下的同一变量,以避免两个或多个线程都“通过”初始检查的竞争:只有一个线程会“通过”第二次检查。
此模式与用于初始化非平凡类型的函数局部静态变量的模式相同(标准要求延迟初始化这些变量)。
\n我们还可以查看“模板静态持有者”模式的程序集,该模式通常用于在 C++17 之前的标头中实现全局变量,如下所示:
\nstruct something {\n something();\n ~something();\n};\n\ntemplate <typename T = void>\nstruct holder {\n static something global;\n};\n\ntemplate <typename T>\nsomething holder<T>::global;\n\n\nvoid instantiate() {\n (void)holder<void>::global;\n}\nRun Code Online (Sandbox Code Playgroud)\n在这里,该类holder允许holder<T>::global在多个翻译单元中实例化,并要求这项工作(“让链接器对其进行排序”),这与非模板类中的命名空间范围全局变量或静态变量的情况不同。该instantiate()调用只是为了实际实例化模板和关联的静态成员,否则根本不会生成任何内容。
组装如下:
\ninstantiate(): # @instantiate()\n ret\n__cxx_global_var_init: # @__cxx_global_var_init\n push rax\n cmp byte ptr [rip + guard variable for holder<void>::global], 0\n je .LBB1_1\n pop rax\n ret\n.LBB1_1:\n mov edi, offset holder<void>::global\n call something::something() [complete object constructor]\n mov edi, offset something::~something() [complete object destructor]\n mov esi, offset holder<void>::global\n mov edx, offset __dso_handle\n call __cxa_atexit\n mov byte ptr [rip + guard variable for holder<void>::global], 1\n pop rax\n ret\nholder<void>::global:\n .zero 1\n\nguard variable for holder<void>::global:\n .quad 0 # 0x0\nRun Code Online (Sandbox Code Playgroud)\n双重检查锁定消失了:守卫变量仅在任何锁之外检查一次。
\n为什么有区别?这只是一个实施怪癖,还是以某种方式源于标准中的要求?
\n看起来这些全局构造函数通常不需要锁,因为这些生成的函数通常在启动时在main到达之前或在动态加载共享对象时在单线程代码中调用。然而,也许有一些我没有考虑的场景,例如并行加载两个引用相同全局的共享对象?