Static initialization order of inline variables in single TU
Asked Answered
E

1

9

I'm aware this question has been asked many times, but this seems to be a slightly different variation which I can't figure out.

Consider the following code:

#include <cstdio>

struct TestValue;

inline const TestValue* v_ptr = nullptr;

struct TestValue {
    static const TestValue v1;

    TestValue() {
        v_ptr = this;
        printf("TestValue Initialized at %p\n", this);
    }
};

struct CallTest {
    CallTest() {
        printf("CallTest Initalized at %p\n", this);
        printf("v_ptr = %p\n", v_ptr);
    }
};

const inline TestValue TestValue::v1{};
const inline CallTest ct{};



int main() {}

I am using C++17 or later, which adds support for extern, static initialized inline variables. I am trying to understand the guarantees around initialization order when using the inline specifier "out of line". Notice that v1 is declared as a static variable of TestValue, then defined inline later, but before ct. Surprisingly (to me at least), using Clang 14.0.3, the program prints:

CallTest Initalized at 0x404059
v_ptr = (nil)
TestValue Initialized at 0x404068

If I move v1 out of TestValue such that it is declared and defined in the same line just before ct, I get the expected output:

TestValue Initialized at 0x404059
CallTest Initalized at 0x404068
v_ptr = 0x404059

I am reasonably confident in my understanding of the standard that this second example is guaranteed to print TestValue first. But what about the first case?

I wasn't sure about the legality of forward declaring v1, then defining it inline later, but that alone seems OK: https://eel.is/c++draft/dcl.inline#note-2

As for the ordering, my understanding is that v1 and ct should be "partially ordered": since they are inline https://eel.is/c++draft/basic.start.dynamic#1

Then, since at least one of them is partially ordered (and the other is not unordered), they are initialized in the order of their definitions: https://eel.is/c++draft/basic.start.dynamic#3.1

Perhaps I'm misreading the definition of partially-ordered and unordered? Is v1 not partially-ordered since the inline specifier comes later in the definition - ie. the ordering only applies to inline at the declaration? In this case I still don't see how it would become unordered; the other possibility is ordered which works. Also specifying inline is needed to fix ODR violations, so it appears to be doing something. (I discovered this error from the above situation but where TestValue and CallTest and their respective definitions were split across multiple headers, the CallTest header including TestValue).

I also find that GCC respects the definition order of v1 and ct while as above, Clang always initializes ct first.

Godbolt Link

Edit: Another observation in Clang - If I make v1 and ct constexpr (Removing the side effects from the constructors), the address of v1 is smaller than ct - they are initialized in the expected order.

I also realized in the above example that const inline CallTest ct{} has internal linkage, while v1 is external being a static member. I fixed this, and ct is still "incorrectly" initialized first. Not sure if this is affects the expected initialization order.

I also ran this test:

extern const TestValue v1;

const inline TestValue v1{};
extern const inline CallTest ct{};

Which initializes v1 first. I don't understand why, if v1 is a static class variable, it would have a different initialization order than as an ordinary extern variable.

Erwinery answered 18/5, 2022 at 9:25 Comment(0)
E
2

Looks like this should be fixed in Clang 16: https://github.com/llvm/llvm-project/commit/f9969a3d28e738e9427e371aac06d71269220123

Demo: https://clang.godbolt.org/z/vMrhTTW8v

Erwinery answered 13/11, 2022 at 1:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.