First, in cppreference The above description of this part is excerpted.
Variables declared at block scope with the static or thread_local specifier have static or thread storage duration, but are not initialized until control first passes through their declaration. In all subsequent calls, the declaration is skipped.
If initialization throws an exception, the variable is not considered initialized, and initialization is attempted again the next time control passes through the declaration.
The behavior is undefined if initialization recursively enters the block of the variable being initialized.
The initialization of static local variables inside a function is performed when the function is first called, and will not be initialized in subsequent calls. In a multi-threaded environment, static local variables can still be guaranteed to be initialized safely and only once.
If multiple threads attempt to initialize the same static local variable at the same time, the initialization occurs exactly once.
The destructor of a block-scoped static variable is called at program exit if initialization has been successful.
Simply put, the static local variables within the function will only be initialized and constructed when the function is called for the first time, and there will be no conflicts in a multi-threaded environment, satisfying lazy initialization and thread safety. And only if it has been initialized, it will be destroyed when the program exits.
original code
Let's use the following code as an example to analyze the specific principles of the compiler's implementation of this feature.
struct example_struct
{
// 此处的 throw 是为了防止编译器将异常处理优化掉
example_struct() { throw; }
~example_struct() { }
};
example_struct& get_example()
{
// 函数内静态局部变量
static example_struct temp;
return temp;
}
C++ Insights
Here, the code generated by C++ Insights is modified based on the assembly results. For details, see 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