10

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?

  • static const is not an option? – John Knoeller Feb 01 '10 at 22:24
  • constness will not work here as the int will get modified later (while a known-to-be-valid mutex is held.) My gut feeling was that any sane compiler would at least zero-initialize static integers at function scope before execution began (in which case, it's good enough for me.) It's the sort of thing that's easy to trip up on, though. – Jonathan Grynspan Feb 01 '10 at 23:36
  • 1
    Technically no. But gcc has an explicit patch to gurantee that it works in a multithreaded enviroment. – Martin York Feb 01 '10 at 23:52
  • Indeed--unfortunately, my code needs to compile for VC++ 9.0 as well, which means that I have to write code that avoids GCC-specific stuff. In this case, the code ultimately functions similarly to pthread_once (which, now that I think about it, would suffer from the same problems in C++) but because of the VC++ requirement can't use pthread_once directly. – Jonathan Grynspan Feb 02 '10 at 00:04
  • @Martin: ... unless it is explicitly disabled using its"-fno-thread-safe-statics" command line option (see http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html). +1, regardless. – Void - Othman Feb 02 '10 at 00:39
  • @Void: Thank you captain obvious. – Martin York Feb 02 '10 at 06:11
  • @Martin: Okay, I deserve that. :) – Void - Othman Feb 02 '10 at 07:41
  • @Jonathan: See this answer: http://stackoverflow.com/questions/449436/singleton-instance-declared-as-static-variable-of-getinstance-method/449823#449823 (ignore the fact its about singletons) But it describes how to solve this problem. – Martin York Feb 02 '10 at 08:45

5 Answers5

10

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.

Adam Bowen
  • 10,820
  • 6
  • 36
  • 41
  • 1
    "A local object of POD type with static storage duration initialized with constant-expressions is initialized before its block is first entered." i.e. not at startup. –  Feb 01 '10 at 23:19
  • Sorry, I've not interpreted that correctly have I. Since I can't find any further clarification I must assume that even POD initialisation is allowed to be deferred to first use of the function, making it not thread safe. – Adam Bowen Feb 02 '10 at 08:35
4

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.

pestilence669
  • 5,698
  • 1
  • 23
  • 35
  • Also, I'd like to see the ASM if someValue actually was modified within the function - I think GCC has done some optimisation here. I have a feeling the resulting code would be more like: http://stackoverflow.com/questions/2180501/in-c-are-static-initializations-of-primitive-types-to-constant-values-thread-s/2180547#2180547 – Justicle Feb 01 '10 at 22:48
  • When I increment the value, it looks exactly as above, except for the increment ASM. Mutating the value introduces a race, but not because of the initialization, which is still absent. – pestilence669 Feb 02 '10 at 01:20
2

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).

R Samuel Klatchko
  • 74,869
  • 16
  • 134
  • 187
2

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.

  • Agh, couldn't find that in the standard. Agh again! The same section says: "The zero-initialization (8.5) of all local objects with static storage duration (3.7.1) is performed before any other initialization takes place." So, if the initial value is 0, the value of 0 should (according to the standard) be valid even in a multithreaded environment, yes? – Jonathan Grynspan Feb 01 '10 at 23:19
  • It certainly seems to say that. –  Feb 01 '10 at 23:21
  • @Jonathan BTW, I wouldn't be too quick to accept this - I'm sure there will be disagreements on the meaning of the quote :-) –  Feb 01 '10 at 23:25
  • If the storage is initialized **before** its block is first entered, how does it follow that it's initialized the first time the function is called? – R Samuel Klatchko Feb 01 '10 at 23:27
  • @ R Samuel Klatchko I said it must be initialise BY the time the function is first called. –  Feb 01 '10 at 23:28
  • I'm putting two bits in the bucket and wishing that nobody does! :P Intuitively though, it seems excessive for a compiler to generate complex code for zero-initialization of static values. – Jonathan Grynspan Feb 01 '10 at 23:34
  • @R Samuel Klatchko "this point" == the point when the static variable MUST have been initialised, which is immediately before the block containing it (in this case the function) is entered for the first time. Note I'm not saying that an implementation must work this way (though it must for non-PODs), but only that it may. –  Feb 01 '10 at 23:45
  • @Neil - you can at least be intellectually honest and note that your original answer said it was "initialized the first time" and only later did you revise that to "by the first time". – R Samuel Klatchko Feb 01 '10 at 23:47
  • @ R Samuel Klatchko I don't need to be honest - you can and presumably have look at the revision history. I'm not trying to deny anything, my edit was made with the intention of making the answer clearer, not making you look bad. In fact, your first comment appeared after the revision in question was made. –  Feb 01 '10 at 23:52
  • @Neil - I'm not complaining about the edit - it was the correct thing to update to make the question accurate. As for your comment... – R Samuel Klatchko Feb 01 '10 at 23:56
  • I didn't mean to cause a ruckus with this question. No need to get mad at each other. :) – Jonathan Grynspan Feb 01 '10 at 23:58
  • @ R Samuel Klatchko When I said "I don't need to be honest" I meant I didn't need to protest my honesty - not well phrased, I admit. –  Feb 01 '10 at 23:58
  • @Jonathan Don't worry - this is about par for the course on SO :-) –  Feb 02 '10 at 00:02
  • "In fact, your first comment appeared after the revision in question was made" - yes, I spent some time thinking about your answer before commenting and did not refresh the page during that time. – R Samuel Klatchko Feb 02 '10 at 00:05
  • @Neil Oh, in that case... carry on. :P – Jonathan Grynspan Feb 02 '10 at 00:06
0

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)

pm100
  • 48,078
  • 23
  • 82
  • 145