C++ – 函数内静态局部变量初始化

首先,在 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

发表回复