在C ++中,原始类型的静态初始化是否为常量值线程安全?

时间:2010-02-01 22:20:50

标签: c++ static multithreading constants

即,即使在多线程环境中,预计还是会正确执行以下内容吗?

int dostuff(void) {
    static int somevalue = 12345;
    return somevalue;
}

或者多个线程可以调用它,还有一个调用可以在执行开始之前返回&somevalue处的垃圾吗?

5 个答案:

答案 0 :(得分:10)

标准的第6.7节有这样的说法:

  

所有本地的零初始化   静态存储持续时间的对象   在任何其他之前执行   初始化发生。本地人   具有静态存储的POD类型的对象   持续时间初始化为   常量表达式已初始化   在首次输入块之前。一个   允许执行   其他本地的早期初始化   静态存储持续时间的对象   在相同的条件下   允许实施   静态初始化一个对象   命名空间中的静态存储时间   范围。 否则这样的对象就是   初始化第一次控制   通过其声明;这样   一个对象被认为是初始化的   完成后   初始化。如果初始化   通过抛出异常退出   初始化不完整,所以它   将在下次再次尝试   控制进入声明。 如果   控制重新进入声明   (递归地),而对象是   正在初始化,行为是   未定义。

因此,如果它是POD类型,那么看起来初始化在启动新线程之前在启动时发生。对于非POD类型,它更复杂,标准说行为是未定义的(除非在其他地方它说明了初始化期间的线程安全性)。

我碰巧知道在初始化非POD对象时,GCC会抓取一个互斥锁以防止它被初始化两次(我知道这是因为我曾经意外地递归初始化一个静态对象而使程序死锁)。

不幸的是,我不能告诉你这是否是其他编译器的情况,或者它是否在标准的其他地方强制执行。

答案 1 :(得分:4)

是的,它是完全安全的(在大多数编译器上)。我建议抛出一个断点,看看如何在你的特定编译器上完成赋值。我不能告诉你违反了多少次“标准”。

如果您从函数或方法调用的结果中分配本地静态,那么您可能正在处理竞争条件。对基本类型的常量分配通常会得到优化。

在用于OS X 10.6.2的g ++上,这是为您的函数生成的机器代码:

push   rbp
mov    rbp,rsp
lea    rax,[rip+0x2067]        # 0x100003170 <_ZZ7dostuffvE9somevalue>
mov    eax,DWORD PTR [rax]
leave  
ret

如您所见,没有任务。编译器在构建时烘焙了原语。

答案 2 :(得分:2)

因为somevalue初始值设定项不需要构造函数调用,所以这样可以正常工作(somevalue将在构建时初始化)。

现在,如果您正在初始化需要构造函数的值:

void whatever()
{
    static std::string value("bad");

    ...
}

然后你可能会遇到多个线程的麻烦。在内部,这将转变为:

void whatever()
{
    static bool value_initialized = false;
    static string_struct value;

    if (!initialized)
    {
        construct_string(&value, "bad");
        value_initialized = false;
    }

    ....
 }

在存在多个线程的情况下,您会遇到各种问题,包括竞争条件和内存可见性。

答案 3 :(得分:2)

从C ++标准,第6.7节:

  

具有静态存储持续时间的POD类型(3.9)的本地对象   用常量表达式初始化   在块之前初始化   第一次进入。

这意味着函数级静态对象必须在第一次输入函数时初始化,而不一定在初始化整个过程时初始化。此时,多个线程可能正在运行。

答案 4 :(得分:0)

根据我的经验,在文件范围定义的静态行为与函数中定义的静态不同

在所有线程开始之前,文件范围1被安全地初始化,而函数范围不是。它是少数几个你无法遵守最小范围规则的地方之一。

请注意,这似乎取决于编译器版本(鉴于我们正在“未定义的”行为区域中行走,您会期望这样做)