Jonathan Grynspan
Jonathan Grynspan

Reputation: 103

In C++, are static initializations of primitive types to constant values thread-safe?

i.e., would the following be expected to execute correctly even in a multithreaded environment?

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

Or is it possible for multiple threads to call this, and one call to return whatever garbage was at &somevalue before execution began?

Upvotes: 10

Views: 1257

Answers (5)

pestilence669
pestilence669

Reputation: 5699

Yes, it's completely safe (on most compilers). I'd recommend throwing in a break point and looking at how the assignment is being done on your particular compiler. I can't tell you how many times "standards" are violated.

If you're assigning a local static from the result of a function or method call, then you will likely be dealing with a race condition. Constant assignment to a primitive type will generally get optimized.

On g++ for OS X 10.6.2, this is the machine code generated for your function:

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

As you can see, there's no assignment. The compiler has baked the primitive in at build time.

Upvotes: 4

pm100
pm100

Reputation: 50210

from my experience the behavior of a static defined at file scope is different from a static defined in a function

The file scope one is safely initialized before all threads get going, the function scope one is not. Its one of the few places where you cannot keep to the minimum scope rule.

Note that this seems to depend on compiler versions (which you would expect given that we are walking in the 'undefined' behavior areas)

Upvotes: 0

anon
anon

Reputation:

From the C++ Standard, section 6.7:

A local object of POD type (3.9) with static storage duration initialized with constant-expressions is initialized before its block is first entered.

This means that a function-level static object must be initialised by the first time the function is entered, not necessarily when the process as a whole is initialised. At this point, multiple threads may well be running.

Upvotes: 2

Adam Bowen
Adam Bowen

Reputation: 11240

Section 6.7 of the standard has this to say:

The zero-initialization of all local objects with static storage duration is performed before any other initialization takes place. A local object of POD type with static storage duration initialized with constant-expressions is initialized before its block is first entered. An implementation is permitted to perform early initialization of other local objects with static storage duration under the same conditions that an implementation is permitted to statically initialize an object with static storage duration in namespace scope. Otherwise such an object is initialized the first time control passes through its declaration; such an object is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control re-enters the declaration (recursively) while the object is being initialized, the behavior is undefined.

So if it's a POD type, then it looks like initialisation happens at startup before new threads can be started. For non-POD types it's more complicated, the standard says the behaviour is undefined (unless somewhere else it says something about thread safety during initialisation).

I happen to know that when initialising a non-POD object, GCC grabs a mutex to prevent it being initialised twice (I know this because I once deadlocked a program by accidentally recursively initialising a static object).

Unfortunately I can't tell you if this is the case for other compilers or it is mandated elsewhere in the standard.

Upvotes: 10

R Samuel Klatchko
R Samuel Klatchko

Reputation: 76601

Because somevalue initializer does not require a constructor call, this will work fine (somevalue will be initialized at build time).

Now, if you were initializing a value that required a constructor:

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

    ...
}

Then you can get into trouble with multiple threads. Internally, this will get turned into something like:

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

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

    ....
 }

In the presence of multiple threads, you have various problems including race conditions and memory visibility).

Upvotes: 2

Related Questions