首先,在 cppreference 上对于这部分的描述节选为。
声明于块作用域且带有 static 或 thread_local 说明符的变量拥有静态或线程存储期,但在控制首次经过其声明时才会被初始化。在其后所有的调用中,声明均被跳过。
如果初始化抛出异常,则不认为变量被初始化,且控制下次经过该声明时将再次尝试初始化。
如果初始化递归地进入正在初始化的变量的块,则行为未定义。
函数内部的静态局部变量的初始化是在函数第一次调用时执行,在之后的调用中不会对其初始化。 在多线程环境下,仍能够保证静态局部变量被安全地初始化,并只初始化一次。
如果多个线程试图同时初始化同一静态局部变量,则初始化严格发生一次。
块作用域静态变量的析构函数在初始化已成功的情况下在程序退出时被调用。
简单说就是函数内静态局部变量仅在函数被第一次调用时才会初始化构造,并且在多线程环境中不会出现冲突,满足懒初始化和线程安全。且仅在被初始化过的情况下,在程序退出时析构。
原始代码
下面我们使用以下代码为例,分析编译器实现该特性的具体原理。
struct example_struct
{
// 此处的 throw 是为了防止编译器将异常处理优化掉
example_struct() { throw; }
~example_struct() { }
};
example_struct& get_example()
{
// 函数内静态局部变量
static example_struct temp;
return temp;
}
C++ Insights
此处根据汇编结果对 C++ Insights 生成的代码有做修正,详见 Github Issue #507 。
struct example_struct
{
inline example_struct() { throw; }
inline ~example_struct() noexcept { }
};
example_struct& get_example()
{
static uint64_t __tempGuard; // 初始化标记与线程锁 未初始化的静态局部变量
alignas(example_struct) static char __temp[sizeof(example_struct)];
if((__tempGuard & 0xff) == 0) // 若变量没有完全完成初始化则执行
{
// 如果变量正在被其他线程初始化 但没有完全完成初始化 会在这里被阻塞
if(__cxa_guard_acquire(&__tempGuard)) // 线程锁加锁
{
try
{
new (&__temp) example_struct(); // 调用构造函数
}
catch(...) // 捕捉构造函数抛出的异常
{
// 线程锁释放
__cxa_guard_abort(&__tempGuard);
throw;
}
// 线程锁释放并标记为初始化完成
__cxa_guard_release(&__tempGuard);
// 设置程序退出时执行的回调析构函数
__cxa_atexit(example_struct::~example_struct, &__temp, &__dso_handle);
}
}
return *reinterpret_cast<example_struct*>(__temp);
}
Compiler Explorer
example_struct::example_struct() [base object constructor]:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
call __cxa_rethrow
example_struct::~example_struct() [base object destructor]:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
nop
pop rbp
ret
get_example():
push rbp
mov rbp, rsp
push r12
push rbx
movzx eax, BYTE PTR guard variable for get_example()::temp[rip]
test al, al
sete al
test al, al
je .L4
mov edi, OFFSET FLAT:guard variable for get_example()::temp
call __cxa_guard_acquire
test eax, eax
setne al
test al, al
je .L4
mov r12d, 0
mov edi, OFFSET FLAT:_ZZ11get_examplevE4temp
call example_struct::example_struct() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZZ11get_examplevE4temp
mov edi, OFFSET FLAT:_ZN14example_structD1Ev
call __cxa_atexit
mov edi, OFFSET FLAT:guard variable for get_example()::temp
call __cxa_guard_release
.L4:
mov eax, OFFSET FLAT:_ZZ11get_examplevE4temp
jmp .L9
mov rbx, rax
test r12b, r12b
jne .L7
mov edi, OFFSET FLAT:guard variable for get_example()::temp
call __cxa_guard_abort
.L7:
mov rax, rbx
mov rdi, rax
call _Unwind_Resume
.L9:
pop rbx
pop r12
pop rbp
ret