c++clinkerstatic-initialization

Is static initialization order fiasco applicable to C?


Everything I found across the internet about static initialization order fiasco was about C++, but is it true that if I initialize global variable of some type Foo like

struct Foo {
    int flag;
    pthread_key_t key;
    void *ptrs[10];
};

I can't initialize variable of type struct Foo like static struct Foo x = { 0 };? if I want to get correct code because of SIOF?


Solution

  • C does not have the static initialization order fiasco. In C89, the rule was:

    All the expressions in an initializer for an object that has static storage duration or in an initializer list for an object that has aggregate or union type shall be constant expressions.

    So a static variable with scalar type could only be initialized with a single constant expression. If the variable's type is an array with a scalar element type, then each initializer would need to be a constant expression, and so on. Since a constant expression can neither produce side effects, nor depend on side effects produced by any other evaluation, changing the evaluation order of constant expressions doesn't affect the result. Furthermore, the compiler can simply emit already-initialized data (i.e., evaluate those constant expressions at compile time), so when the program starts, there is no static initialization to be done.

    The only non-constant expressions that can be evaluated before main are ones that are invoked from the C runtime. That's why the FILE objects that are pointed to by stdin, stdout, and stderr are already available for use by the first statement of main, for example. Standard C doesn't allow users to register their own startup code to be run before main—although GCC does provide an extension called __constructor__ (presumably named after the C++ feature) that you can use to recreate the static initialization order fiasco in C if you so desire.

    Stroustrup wrote in The Design and Evolution of C++ that his aim was to make user-defined types usable wherever built-in types were. That meant that C++ had to allow global variables of class type, which means that their constructors would get called during program startup. Because early C++ didn't have constexpr functions, such constructor calls could never be constant expressions. And so, the static initialization order fiasco was born.

    During the C++ standardization process, the question of the order in which to perform static initialization was a controversial topic. I think most people would agree that the ideal situation would be for every static variable to be initialized prior to its use. Unfortunately, that requires linker technology that didn't exist in those days (and probably still doesn't?). The initialization of a static variable can involve function calls, and those functions may be defined in another TU, which means you would need to perform whole-program analysis in order to successfully topologically sort the static variables in dependency order. It's worth noting that even if C++ could have been designed this way, it still wouldn't have completely prevented initialization order issues. Imagine if you had some library where a precondition of the use function was that the init function had been called at some point in the past. Then, if you have one static variable whose initializer calls init and another whose initializer calls use, then there's an order dependency that the compiler can't see.

    Ultimately, the limited initialization order guarantees that we got in C++98 were the best that we could get under the circumstances. With the benefit of unlimited hindsight, perhaps one could have protested that the standard wouldn't be complete without constexpr functions (and that static variables should be required to only have constant initialization).