如何初始化非POD静态值?

时间:2014-07-04 07:20:22

标签: c++ static

与其他语言不同,C ++允许静态数据为任意类型,而不仅仅是普通旧数据。普通旧数据初始化很简单(编译器只是将值写入数据段中的适当地址),但其他更复杂的类型则不是。

如何在C ++中实现非POD类型的初始化?特别是,第一次执行函数foo时到底发生了什么?有哪些机制用于跟踪str是否已经初始化?

#include <string>
void foo() {
    static std::string str("Hello, Stack Overflow!");
}

4 个答案:

答案 0 :(得分:4)

C ++ 11要求函数local static变量的初始化是线程安全的。因此,至少在兼容的编译器中,通常会使用某种同步原语,每次输入函数时都需要对其进行检查。

例如,这是该程序代码的assembly listing

#include <string>
void foo() {
    static std::string str("Hello, Stack Overflow!");
}

int main() {}

.LC0:
    .string "Hello, Stack Overflow!"
foo():
    cmpb    $0, guard variable for foo()::str(%rip)
    je  .L14
    ret
.L14:
    pushq   %rbx
    movl    guard variable for foo()::str, %edi
    subq    $16, %rsp
    call    __cxa_guard_acquire
    testl   %eax, %eax
    jne .L15
.L1:
    addq    $16, %rsp
    popq    %rbx
    ret
.L15:
    leaq    15(%rsp), %rdx
    movl    $.LC0, %esi
    movl    foo()::str, %edi
    call    std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
    movl    guard variable for foo()::str, %edi
    call    __cxa_guard_release
    movl    $__dso_handle, %edx
    movl    foo()::str, %esi
    movl    std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string(), %edi
    call    __cxa_atexit
    jmp .L1
    movq    %rax, %rbx
    movl    guard variable for foo()::str, %edi
    call    __cxa_guard_abort
    movq    %rbx, %rdi
    call    _Unwind_Resume
main:
    xorl    %eax, %eax
    ret

__cxa_guard_acquire__cxa_guard_release等正在保护静态变量的初始化。

答案 1 :(得分:1)

我见过的实现使用隐藏的布尔变量来检查变量是否已初始化。现代编译器可以安全地执行此操作,但是IIRC,一些较旧的compilerd没有这样做,如果它同时从多个线程调用,你可以得到两次调用的构造函数。

有些事情:

static bool __str_initialized = false;
static char __mem_for_str[...]; //std::string str("Hello, Stack Overflow!");

void foo() {
    if (!__str_initialized)
    {
        lock();
        __str_initialized = true;
        new (__mem_for_str) std::string("Hello, Stack Overflow!");
        unlock();
    }
}

然后,在程序的最终代码中:

if (__str_initialized)
     ((std::string&)__mem_for_str).~std::string();

答案 2 :(得分:0)

具体实施。

通常,会有一个标志(静态初始化为零)以指示它是否已初始化,以及(在C ++ 11中,或更早的线程安全实现中)某种互斥锁,也是静态初始化,以防止多个线程尝试初始化它。

生成的代码通常表现为

static __atomic_flag_type __initialised = false;
static __mutex_type __mutex = __MUTEX_INITIALISER;

if (!__initialised) {
    __lock_type __lock(__mutex);
    if (!__initialised) {
        __initialise(str);
        __initialised = true;
    }
}

答案 3 :(得分:0)

您可以通过生成汇编列表来检查编译器的功能。

MSVC2008在调试模式下生成此代码(不包括异常处理prolog / epilog等):

    mov eax, DWORD PTR ?$S1@?1??foo@@YA_NXZ@4IA
    and eax, 1
    jne SHORT $LN1@foo
    mov eax, DWORD PTR ?$S1@?1??foo@@YA_NXZ@4IA
    or  eax, 1
    mov DWORD PTR ?$S1@?1??foo@@YA_NXZ@4IA, eax
    mov DWORD PTR __$EHRec$[ebp+8], 0
    mov esi, esp
    push    OFFSET ??_C@_0BH@ENJCLPMJ@Hello?0?5Stack?5Overflow?$CB?$AA@
    mov ecx, OFFSET ?str@?1??foo@@YA_NXZ@4V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@A
    call    DWORD PTR __imp_??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@PBD@Z
    cmp esi, esp
    call    __RTC_CheckEsp
    push    OFFSET ??__Fstr@?1??foo@@YA_NXZ@YAXXZ   ; `foo'::`2'::`dynamic atexit destructor for 'str''
    call    _atexit
    add esp, 4
    mov DWORD PTR __$EHRec$[ebp+8], -1
$LN1@foo:

?$S1@?1??foo@@YA_NXZ@4IA引用的静态变量,检查它是否与&amp; 1为零。如果不是,则分支到标签$LN1@foo:。否则它或者在标志的1中,在已知位置构造字符串,然后使用'atexit'在程序出口处为其析构函数添加一个调用。然后正常继续该功能。