Is the initialization order of global variables and static inline data members relative to each other guaranteed?
Asked Answered
C

1

6

The code below is in the same translation unit and A::v is defined after x, why A::v is not initialized to "ok" ?

#include <string>
#include <iostream>

std::string foo() {
    return "OK";
}

std::string x = foo();


struct A {
    static inline std::string v = x;
};

int main() {
    std::cout << A::v << std::endl; // didn't print "OK", why?
}

Carrew answered 26/9, 2023 at 9:38 Comment(7)
cannot reproduce, on your link in prints OK for me...Independence
#1422171: I would tend to say that, if order of definition is respected, your code should work as expected.Independence
on gcc it works, on clang it did not... interesting!Madsen
I would argue that if your code depends on initialization order, even within the same translation unit, it's a flawed design. Or a flawed implementation of the design.Senskell
Inline initialization of non-integral static member A::v and x got dynamic initialization results in that A::v isn't guaranteed to be initialized until constructor is called, without that order between initializing x and v is unkniwnDues
@Swift-FridayPie according to your argument x would always be initialized before A::v anyway, wouldn't it?Independence
using out of line initialisation of v works with clang (godbolt.org/z/nbbGjnezT) : strange. Wouldn't it be a compiler bug? The rules given in #1422171 (already mentioned) seem in favor of the order of initialization being as expected in this case. (Maybe I misinterpreted it)Independence
K
8

Per [basic.start.dynamic]/1, a non-inline non-templated non-block variable with static storage duration has "ordered" initialization, while an inline non-templated non-block variable with static storage duration has "partially ordered" initialization.

Per [basic.start.dynamic]/3, we only have an initialization order guarantee between two non-block variables with static storage duration when:

  • the first and second variables both have ordered initialization, and the definition of the first variable precedes the definition of the second variable, or
  • the first variable has partially ordered initialization, the second variable has ordered initialization, and the definition of the second variable is preceded by a definition of the first variable, or
  • both variables have partially ordered initialization, and every definition of the second variable is preceded by a definition of the first variable.

Therefore, if the non-inline variable is defined before the inline variable, we don't have an initialization order guarantee. We only have an initialization order guarantee when the inline variable is first. So in this code, v can be initialized before x, resulting in undefined behaviour.

Karlmarxstadt answered 26/9, 2023 at 12:47 Comment(15)
As an example: if we make x inline it is guaranteed to be ordered before both y as well as A::v, and the program is well-formed (demo).Protoxylem
Unsure about UB though, zero initialization has taken place, so v is either empty or "OK"s, no ("regular" SIOF)?Repressive
@Repressive You cannot assume that zero initialization produces an empty string. std::string is not trivially copyable, so even if you observe an empty string to have an all-zeroes bit pattern, it does not mean that zeroing a different std::string object gives it the same value.Karlmarxstadt
@Repressive Wouldn't zero-initialization of a std::string object qualify as "indeterminate" (implementation-defined) rather than empty?Protoxylem
Gg. For my understanding what means block in this context? Merely inside braces whatever there containing scope?Independence
@dfrib: implementation-defined would be more correct than empty (and that implementation-defined value might be an invalid state I suppose). My question was more which part is UB? v = x; with x uninitialized (I don't think so), with x potentially invalid (that I think now), so with user-defined class, we "know" what produce zero-initialization.Repressive
@Repressive I've never really managed to clearly understand whether "implementation-defined which may, based on implementation details, result in UB" should be categorized as "undefined" or "implementation-defined".Protoxylem
@Independence A block variable is a variable whose target scope is a block scope. (So an extern variable declared at block scope is not a block variable, because its target scope is an enclosing namespace scope.)Karlmarxstadt
@Protoxylem But it's not implementation-defined in this case. It's unspecified. The layout of std::string is implementation-defined, but whether or not zero-initializing it actually produces a valid value is unspecified.Karlmarxstadt
@BrianBi I see, thanks. This reminds of the C++17 change to timsong-cpp.github.io/cppwp/n4868/expr.static.cast#10 where the behavior converting an (implementation-defined) out-of-range enumerator of an unscoped enum with no underlying type was changed from an unspecified to undefined result. From a client/programmer perspective, is unspecified ever less bad than undefined, if the result of the unspecificity could lead to UB?Protoxylem
@BrianBi ah, the extern example is confusing me and I found the cppreference definition not really enlightening (en.cppreference.com/w/cpp/language/scope, en.cppreference.com/w/cpp/language/…). AFAIU, it seems to be anything between braces except extern declaration as you mentioned?Independence
maybe I'm overcomplicating things: {} is a block where variable may be declared, with the exception of extern variables because its injecting a declaration from an enclosing scope. Am I right?Independence
@Independence Well, you have to exclude braces that enclose namespaces and classes...Karlmarxstadt
I think, I see. Thanks for your patient explantions.Independence
@Protoxylem We try to avoid "unspecified behavior" because it, indeed, is no better than undefined behavior: the unspecified behavior can always include executing an operation that has undefined behavior.Karlmarxstadt

© 2022 - 2024 — McMap. All rights reserved.