C++ - Static local variable initialization within function

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

Portal

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

Portal

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

Post Reply