Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to create and use vectors during static initialization?

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);
    
like image 616
aecolley Avatar asked Jun 16 '14 23:06

aecolley


2 Answers

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.

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

like image 55
Lightness Races in Orbit Avatar answered Oct 06 '22 01:10

Lightness Races in Orbit


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.

like image 20
Tony Delroy Avatar answered Oct 06 '22 00:10

Tony Delroy