8

I have C++ code which declares static-lifetime variables which are initialized by function calls. The called function constructs a vector instance and calls its push_back method. Is the code risking doom via the C++ static initialization order fiasco? If not, why not?

Supplementary information:

  1. What's the "static initialization order fiasco"?

    It's explained in C++ FAQ 10.14

  2. Why would I think use of vector could trigger the fiasco?

    It's possible that the vector constructor makes use of the value of another static-lifetime variable initialized dynamically. If so, then there is nothing to ensure that vector's variable is initialized before I use vector in my code. Initializing result (see code below) could end up calling the vector constructor before vector's dependencies are fully initialized, leading to access to uninitialized memory.

  3. What does this code look like anyway?

    struct QueryEngine {
      QueryEngine(const char *query, string *result_ptr)
        : query(query), result_ptr(result_ptr) { }
    
      static void AddQuery(const char *query, string *result_ptr) {
        if (pending == NULL)
          pending = new vector<QueryEngine>;
        pending->push_back(QueryEngine(query, result_ptr));
      }
    
      const char *query;
      string *result_ptr;
    
      static vector<QueryEngine> *pending;
    };
    
    vector<QueryEngine> *QueryEngine::pending = NULL;
    
    void Register(const char *query, string *result_ptr) {
      QueryEngine::AddQuery(query, result_ptr);
    }
    
    string result = Register("query", &result);
    
aecolley
  • 1,973
  • 11
  • 10
  • The answer is "No, the functionality of `std::vector` is not directly dependent on any static initialization", but one of the standards gurus will have to show up here with the proper quote. – Drew Dormann Jun 16 '14 at 23:49
  • 1
    @DrewDormann "*but one of the standards gurus will have to show up here with the proper quote*" :D so true ^^ – Stefan Falk Jun 17 '14 at 00:46

2 Answers2

4

Fortunately, static objects are zero-initialised even before any other initialisation is performed (even before the "true" initialisation of the same objects), so you know that the NULL will be set on that pointer long before Register is first invoked.1

Now, in terms of operating on your vector, it appears that (technically) you could run into such a problem:

[C++11: 17.6.5.9/3]: A C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s non-const arguments, including this.

[C++11: 17.6.5.9/4]: [Note: This means, for example, that implementations can’t use a static object for internal purposes without synchronization because it could cause a data race even in programs that do not explicitly share objects between threads. —end note]

Notice that, although synchronisation is being required in this note, that's been mentioned within a passage that ultimately acknowledges that static implementation details are otherwise allowed.

That being said, it seems like the standard should further state that user code should avoid operating on standard containers during static initialisation, if the intent were that the semantics of such code could not be guaranteed; I'd consider this a defect in the standard, either way. It should be clearer.

1 And it is a NULL pointer, whatever the bit-wise representation of that may be, rather than a blot to all-zero-bits.

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • "technically, you could run into such a problem" - into "a" problem, yes, but "*such* a problem"? - you've documented a different issue than that raised in the question, which doesn't mention threading. – Tony Delroy Jun 17 '14 at 00:21
  • @TonyD: You've misread my answer. Threading is tangential: the passage talking about it in the standard is the same passage that suggests `vector` could use `static` objects internally as long as they use synchronisation around them, but synchronisation doesn't prevent static initialisation order fiasco issues, which _is_ what the OP was asking about. – Lightness Races in Orbit Jun 17 '14 at 00:31
  • "Fortunately, static objects are zero-initialised" - is it guaranteed to be a null pointer, or does it get whatever all-bits-zero is? – M.M Jun 17 '14 at 00:48
  • I think its an interesting approach to demonstrate the possibility. Now I'm going to give you an absolute concrete example of the real problem in static vectors or other allocating objects : the allocator. At least in C++03 allocators are stateless, which makes them reference mandatorily a global system. In the default case new and malloc. themselves manipulating static global data managed by the CRT. If its malloc maybe somewhere the standard guarantees that it is callable from any static compilation unit. but a custom allocator wont have this guarantee, and boom. – v.oddou Jun 17 '14 at 00:51
  • @LightnessRacesInOrbit: oh ok - makes sense, though of course the Standard saying that it allows Standard library functions to do this doesn't mean anything in `vector` specifically would do so, much less that any static data it might use required dynamic initialisation, but I appreciate you're aware of all that. A great many real world applications rely on `vector` and `map` to behave well in this usage. – Tony Delroy Jun 17 '14 at 01:15
  • @TonyD: I agree; a sane implementation would avoid doing this. But in terms of global compliance I'd be wary of relying on it in production code; y'know, just in case. – Lightness Races in Orbit Jun 17 '14 at 08:41
  • @MattMcNabb: Yeah [you'll get a null pointer](http://stackoverflow.com/a/21964142/560648). I've now added this detail (and the same cross-reference) to the answer in a footnote. – Lightness Races in Orbit Jun 17 '14 at 08:42
  • @v.oddou: 3.7.4.1/4 clarifies, in a note, that: "In particular, a global allocation function is not called to allocate storage for objects with static storage duration", though that doesn't really prohibit the scenario you're saying since a non-global allocation function can have global state too. It's definitely something to watch out for. I try to avoid the entire `static` mess as much as possible. – Lightness Races in Orbit Jun 17 '14 at 08:49
2

vector doesn't depend on anything preventing its use in dynamic initialisation of statics. The only issue with your code is a lack of thread safety - no particular reason to think you should care about that, unless you have statics whose construction spawns threads....

Initializing result (see code below) could end up calling the vector constructor before that class is fully initialized, leading to access to uninitialized memory.

No... initialising result calls AddQuery which checks if (pending == NULL) - the initialisation to NULL will certainly have been done before any dynamic initialisation, per 3.6.2/2:

Constant initialization is performed:

...

if an object with static or thread storage duration is not initialized by a constructor call and if either the object is value-initialized or every full-expression that appears in its initializer is a constant expression

So even if the result assignment is in a different translation unit it's safe. See 3.6.2/2:

Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.

Community
  • 1
  • 1
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • My question wasn't about whether the "pending" variable contained garbage; I've edited the question now in the hope of eliminating that ambiguity. I am concerned only with whether vector depends on other statics completing (dynamic) initialization. I can easily believe that there's no good reason why vector should have such a dependency; but if it isn't specified by the standard, it would be reckless of me to rely on that. – aecolley Jun 17 '14 at 00:43
  • @aecolley: "it would be reckless" - no it wouldn't - there's a *mountain* of code using exactly this kind of registration (and literature documenting it) - if there were issues in any real world Standard library implementations they'd be well documented (and fixed) long ago. – Tony Delroy Jun 17 '14 at 01:35
  • I think it _is_ reckless. Just because no current issue is known to exist with real-world implementations doesn't mean that relying on a non-guarantee is a wise idea. Things can change at any point. By your logic, you might as well rely on `(void*)0` producing a segfault "because most systems today will do that"; it's just not the way to write portable code, at all. Maybe it's more of a matter of principle, though ;P – Lightness Races in Orbit Jun 17 '14 at 08:51
  • @LightnessRacesinOrbit there's never any need to rely on bad pointers producing segfaults, but this kind of static registration is a common technique allowing libraries to be self-registering with object factories and service directories, and there's really no alternative that doesn't require a different kind of coupling where the registration system is actively told about the additional library. Further, use of Standard containers is so pervasive that an enormous number of objects wouldn't be safe to have static instances of if this were ever broken. It's just not plausible. – Tony Delroy Jun 17 '14 at 14:00
  • @TonyD: As an aside, I've found static self-registration to be entirely unreliable within shared libraries, at the very least. I now avoid it. – Lightness Races in Orbit Jun 17 '14 at 20:29
  • @LightnessRacesinOrbit without getting into implementation details, I can't know what to make of that. – Tony Delroy Jun 18 '14 at 01:00