2

Here in cppref says,

If the initialization of a non-inline variable (since C++17) is deferred to happen after the first statement of main/thread function, it happens before the first odr-use of any variable with static/thread storage duration defined in the same translation unit as the variable to be initialized.

And later it gives an example of deferred dynamic initialization:

// - File 1 -
#include "a.h"
#include "b.h"
B b;
A::A(){ b.Use(); }

// - File 2 -
#include "a.h"
A a;

// - File 3 -
#include "a.h"
#include "b.h"
extern A a;
extern B b;

int main() {
  a.Use();
  b.Use();
}

And the comment says:

If a is initialized at some point after the first statement of main (which odr-uses a function defined in File 1, forcing its dynamic initialization to run), then b will be initialized prior to its use in A::A

Why can the if situation happen? Doesn't a.Use() odr-use a thus a must be initialized before this statement?

xskxzr
  • 12,442
  • 12
  • 37
  • 77
choxsword
  • 3,187
  • 18
  • 44
  • I'm confused. Why do you think `a` is not initialised? – Lightness Races in Orbit Apr 13 '18 at 10:40
  • @LightnessRacesinOrbit Because in `a`'s constructor it calls `b.Use()`.If `a` is constructed,then `b` will also be constructed.But the cppref says `b` is initialized in `a.Use()`,not `A::A()`. – choxsword Apr 13 '18 at 10:41
  • @LightnessRacesinOrbit And the quoted paragraph says:"If `a` is initialized at some point after the first statement of `main`",which means that `a` is initialized after `a.Use()`. – choxsword Apr 13 '18 at 10:46
  • Sorry, I'm not really following that, but I'm also not really trying to. Obviously an object will be initialised before you call functions on it. – Lightness Races in Orbit Apr 13 '18 at 10:52
  • "_'If a is initialized at some point after the first statement of main',which means that a is initialized after a.Use()"_ That's not what it means at all. It says _if_. – Lightness Races in Orbit Apr 13 '18 at 10:52
  • 1
    `a.Use()` forces to "Load" File2 (initialize `a`). Creating `a` forces to "Load" File3 (and then initialize `b`). – Jarod42 Apr 13 '18 at 10:54
  • @LightnessRacesinOrbit But there is a **then** after **if**.That's what I'm confused. – choxsword Apr 13 '18 at 10:54
  • @Jarod42 The quoted paragraph says " `a` is initialized at some point after the first statement of `main` ",which means that `a` is initialized after `a.Use()`" – choxsword Apr 13 '18 at 10:56
  • Read *"after the first statement of `main`"* at just after `{` of main. globals might be initialized before main is reached. here, the sentence means that we are in main. – Jarod42 Apr 13 '18 at 10:57
  • @Jarod42 Is `{` a [statement](http://en.cppreference.com/w/cpp/language/statements) ? And globals are not always initialized before `main`, the quoted code snippet is a exception,which says **deferred initialization**. – choxsword Apr 13 '18 at 10:59
  • @LightnessRacesinOrbit To be more precise,**if....then** means this situation may happen,and I'm confuse about that situation. – choxsword Apr 13 '18 at 11:01
  • I think the wording of cppreference is not clear/exact here. – Jarod42 Apr 13 '18 at 11:01
  • @Jarod42 I do not think so,because the sentence " _`b` will be initialized prior to its use in `A::A`"_ does mean that `b` is not initialized in `a`'s constructor. – choxsword Apr 13 '18 at 11:02
  • @bigxiao: The ***then*** is completely irrelevant when the ***if*** is not satisfied. That's what "if" means. – Lightness Races in Orbit Apr 13 '18 at 11:07
  • @LightnessRacesinOrbit But here in this question I'm curious about what will happen when **if** is satisfied,that's why I asked this question. – choxsword Apr 13 '18 at 11:10
  • @LightnessRacesinOrbit I provide a concrete test case,you can see my edited question. – choxsword Apr 13 '18 at 12:04
  • @Jarod42 See my newly added test case. – choxsword Apr 13 '18 at 12:04
  • This is getting closer to a question now. What version of GCC is this? That looks potentially buggy to me, though I don't really know anything about the new C++17 rules. – Lightness Races in Orbit Apr 13 '18 at 12:06
  • @LightnessRacesinOrbit g++ 6.3.0 – choxsword Apr 13 '18 at 12:07
  • 1
    @LightnessRacesinOrbit I think this test case actually has nothing to do with what cppref says and the original question. It's just an undefined behavior because the initialization sequence of globals in different compile unit are unspecified. So " an object will be initialised before you call functions on it" is not true under such situation. – choxsword Apr 13 '18 at 12:15
  • @bigxiao: You miss the point of TU dependencies. You would have to move definition of `A::Use()` in b.cpp. – Jarod42 Apr 13 '18 at 12:55
  • I have edited the question to make it clear. If I changed your original meaning, please tell me and roll back the edit. – xskxzr Apr 13 '18 at 17:09

3 Answers3

0

I think you're being misled by the order of things in C++.

Translation Units (TU= one .cpp files and its headers) have no order in C++. They can be compiled in any order, but also in parallel. The only special TU is the one that contains main(), but even that one can be compiled at any time and order.

Within each Translation Unit, there is an order in which initializers appear. This is also the temporal order in which they are initialized, but it might differ from their order in memory (if that is even determined - C++ strictly speaking does not enforce that). This does not cause an order of initializers across Translation Units. It does happen before functions of that Translation Unit are executed, because those functions may rely on the initialized objects.

Functions in a translation unit can of course appear in any order; how they are executed depends on what you've written in them.

Now there are a few things that impose additional ordering constraints. I understand that you are aware of the fact that some initializers can run even after main() has started. If this happens, the ordinary rule still applies that the initializers of a single TU must execute befor functions in that TU.

In this case, TU file1 holds the (default) initializer for b, which must run before A::A in the same TU. As you correctly note, a.Use must happen after the initialization of a. This requires A::A.

Hence, we have the following order relations (where < means precedes)

b < A::A
A::A < a
a < a.Use

and therefore transitively

b < a.Use

As you can see, it's safe to use a.c in a.Use because the order A::A < a.Use also holds.

You can get into problems, if you make A::A depend on b and B::B depend on a. If you introduce a cyclic dependency, no matter which object is initialized first, it always depend on an object that hasn't been initialized. Don't do that.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • I thought you misunderstand the question, what I'm confused here is the **odr-use**,not the initialization order. – choxsword Apr 13 '18 at 13:18
  • @bigxiao: I'm not confused at all. The **odr-use** of `a` is why I give the order relation `a < a.Use`, the initialization of `a` definitely happens before `a.Use`. There's also an order `b – MSalters Apr 13 '18 at 13:26
  • Why `a < a.Use`? `a.Use` could be called before `a` is constructed. – choxsword Apr 13 '18 at 13:33
  • @bigxiao: No, it can't, for the precise reason you gave at the start of your question. – MSalters Apr 13 '18 at 13:47
  • See [this question](https://stackoverflow.com/questions/49817446/is-object-guaranteed-to-be-initialized-before-calling-its-member-function).There's no guarantee. – choxsword Apr 13 '18 at 13:56
0

In short, why bother the order of initialization of a and b?

The example shows nothing that indicates a should be necessarily initialized before b to make the program well-defined.

  • It is true that extern A a; is before extern B b;, but this is nothing to do with the order.

  • It is also true that evaluation in a.Use(); is sequenced before evaluation in b.Use(); in the main function in the TU translated from File 3, but this is still nothing to do with the order.

Making a.Use() to be well-defined has nothing to do with this particular order, unless there are other dependencies (e.g. subobject initialization implies order).

OTOH, if you want the additional order, how do you specify it?

Annex:

The wording "happen after the first statement of main/thread function" is strange. It seems that the intentional one is "does not happen before the first statement of main/thread function", and the editor occasionally missed there can be more than one evaluations applicable to the binary relationship in evaluation of a single statement. This originates from the standard, but it has been corrected by P0250R3.

Actually, I find the example comes from the standard, quoted from N4727 [basic.start.dynamic]/4:

3 A non-initialization odr-use is an odr-use (6.2) not caused directly or indirectly by the initialization of a non-local static or thread storage duration variable.

4 It is implementation-defined whether the dynamic initialization of a non-local non-inline variable with static storage duration is sequenced before the first statement of main or is deferred. If it is deferred, it strongly happens before any non-initialization odr-use of any non-inline function or non-inline variable defined in the same translation unit as the variable to be initialized.55 It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs. [ Note: Such points should be chosen in a way that allows the programmer to avoid deadlocks. —end note ] [ Example:

// - File 1 -
#include "a.h"
#include "b.h"
B b;
A::A(){
    b.Use();
}

// - File 2 -
#include "a.h"
A a;

// - File 3 -
#include "a.h"
#include "b.h"
extern A a;
extern B b;
int main() {
    a.Use();
    b.Use();
}

It is implementation-defined whether either a or b is initialized before main is entered or whether the initializations are delayed until a is first odr-used in main. In particular, if a is initialized before main is entered, it is not guaranteed that b will be initialized before it is odr-used by the initialization of a, that is, before A::A is called. If, however, a is initialized at some point after the first statement of main, b will be initialized prior to its use in A::A. —end example ]

55) A non-local variable with static storage duration having initialization with side effects is initialized in this case, even if it is not itself odr-used (6.2, 6.6.4.1).

FrankHB
  • 2,297
  • 23
  • 19
  • So,is it right that: 1. Calling member function is odr-use of that object. 2. All objects in one TU are initialized before odr-use happens to any object in that TU,that is,if odr-use of certain object is going to happen,then all objcects in that TU are required to be initialized,no matter whether they are going to be odr-used or not? – choxsword Apr 14 '18 at 02:06
  • 1. When it is non-trivial. It should be true for every user-defined function as I known. 2. No. (a) You'd exclude local static variables and thread-local variables, will you? (b) Both odr-use and the existence of initialization of non-local static variables during program initiation are static property of a program. Requirements of initialization are not effected, only the _order_ of execution is allowed to change. If the program doesn't exit during initialization, all of them would be done. Also note odr-use is only referenced in dynamic initialization rules, not traditional ones. – FrankHB Apr 14 '18 at 06:08
  • OK,from what you quoted,it seems that I can draw two conclusions: 1. If the dynamic initialization is not delayed,then `b` may not be initialized before its odr-use,which may cause **undefined behavior**. 2. If it's delayed,then `b` is guaranteed to be initialized before `A:A()` because the `A::A()` and `b` are in the same TU. Am I right? – choxsword Apr 14 '18 at 13:55
0

I think the comment in the cppreference example simply doesn't match what the example is trying to convey. It should be phrased in terms of the initialization of b, not that of a:

  • If b's initialization is not deferred, then there's no guarantee that it takes place before a's initialization. The compiler might choose some order such as (1) initialize TU 2, (2) initialize TU 1, (3) execute main(). (Note that these steps might also be interleaved.)
  • If b's initialization is deferred, then it must take place before any odr-use of any non-inline variable or non-inline function that's defined in the same translation unit as b. Since A::A is defined in the same translation unit as b, that means b must be initialized before A's constructor is called.

However, the entire analysis isn't correct anymore, because P0250R3 changed the rule so that the initialization is only required to occur before any "non-initialization odr-use" of non-inline variables and non-inline functions defined in the same translation unit. The odr-use of A::A is only caused by the initialization of a, so it is not a non-initialization odr-use, so there is no guarantee that the initialization of b occurs before it.

(I'm not entirely sure why they made this change, but at least in this case, the result makes sense. Deferred initialization shouldn't give you extra ordering guarantees between initializations compared with non-deferred initialization, and if you were relying on such ordering guarantees, your code was non-portable and extremely hard to understand; hopefully you have fixed it by now.)

Brian Bi
  • 111,498
  • 10
  • 176
  • 312