Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The issue about the deferred dynamic initialization

Consider the example in the section basic.start.dynamic, that is:

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

// - 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();  //#2
  b.Use();
}

The comments follow the example are:

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.

I can't understand why b is guaranteed initialized prior to its use in A​::​A when a is initialized at some point after the first statement of main. According to what the rule says:

basic.start.dynamic#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.

basic.start.dynamic#3

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

What I can understand is that, when the initialization is deferred, the variable a should be initialized prior to the odr-use(non-initialization odr-use) of the variable a which is at the place marked with #2. However what I can't understand is that, the comment says that b will be initialized prior to its use in A​::​A. IIUC, the invocation of function A::A is as a part of initialization of the variable a, hence the odr-use of the variable b at #1 is not a non-initialization odr-use due to it is caused directly or indirectly by the initialization of a non-local static or thread storage duration variable. I think it only can say that the variable b is guaranteed to be initialized prior to #2, why the comment says that b will be initialized prior to its use in A​::​A? How to interpret this example?

like image 949
xmh0511 Avatar asked Oct 26 '20 11:10

xmh0511


People also ask

What is the benefit of dynamic initialization?

Why we need the dynamic initialization? It utilizes memory efficiently. Various initialization formats can be provided using overloaded constructors. It has the flexibility of using different formats of data at run time considering the situation.

What do you mean by dynamic initialization explain with example?

The process of initializing a variable at the moment it is declared at runtime is called dynamic initialization of the variable. Thus, during the dynamic initialization of a variable, a value is assigned to execution when it is declared. Example: main() { Int a; cout<<“Enter Value of a”; cin>>a; int cube = a * a * a; }

Which statement is a example of dynamic initialization?

int a=cube(n); In the above program code , a is a global variable to which a number n is dynamically assigned through a function cube , where cube() performs the cube of a number. This is an example of Dynamic Initialization.

What is the difference between normal initialization and dynamic initialization?

question. The difference between static initialization and dynamic initialization: A static initialization in java is a particular block that is made to run before the main () method in java whereas dynamic initialization refers to the initiation of the project during the rum time.


Video Answer


1 Answers

Evolution of the clause

The (non-normative) example in question dates back as far as the C++98 edition of the Standard, but the (normative) language in the hosting clause was changed in C++17.

C++98:

3.6.2 Initialization of non-local objects [basic.start.init]

3 - It is implementation-defined whether or not the dynamic initialization ([cross-references]) of an object of namespace scope is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first use of any function or object defined in the same translation unit as the object to be initialized. [footnote regarding side-effects] [Example follows]

C++03 has the same text. C++11 removes the cross-references and replaces "object of namespace scope" with "non-local variable with static storage duration", "object" with "variable", and "use" with "odr-use", but I would submit that the meaning of the clause is unaltered. C++14 makes no change.

The language was then changed by P0250R3, published and transcribed into the draft Standard in March 2017, just in time to make it into C++17. P0250R3 added the definition of non-initialization odr-use and amended the clause to refer to that definition, while also expressing the relation between events in threading-aware terms (sequenced before, strongly happens before etcetera), and added a note on avoiding deadlock.

Since then, the note on avoiding deadlock has been amended to a Recommended practice.

Motivation of the wording change

It is fortunate that P0250R3 includes discussion of motivation. In the section Parallel initialization for sequential programs we read:

Currently, we very explicitly allow static constructors to run after the start of main, whether or not other threads are started. This appears to be motivated by the intent to support e.g. lazily loading a dynamic library when a function symbols is referenced, as with RTLD_LAZY on Posix systems. Even if static namespace-scope constructors are run immediately in library loading, the library may be implicitly loaded after the start of main.

And also:

SG1 generally feels that static namespace-scope constructors should be avoided [...] we decided to restrict such constructors to existing threads, which appears to be consistent with known implementations.

Correctness of the example.

I submit that the example is and always has been incorrect.

In C++98 the example is incorrect because the normative wording in that edition of the Standard leads to circularity. Suppose we augment the example to define the constructor B::B in the same TU as the definition of a:

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

Now per C++98 the (dynamic) initialization of a occurs before the first call to B::B, and also the initialization of b occurs before the first call to A::A. But the initialization of a requires a call to A::A, and the initialization of b requires a call to B::B. So we have a circular regress.

The wording change in P0250R3 (changing odr-use to non-initialization odr-use) breaks this circularity, at the cost of making the example nonsensical. But then it was always broken. This is the SIOF, which can be avoided via the Construct on First Use idiom or via the use of helper objects such as ios_base::Init.

Implementation practice

I compiled the example (with circularity) into a (Linux, ELF; CentOS 7.8) shared object, loaded into the program after main was entered using dlopen. Precisely one of a and b was odr-used in an uninitialized state, which one dependent on link ordering.

This demonstrates that the change in wording to non-initialization odr-use reflects implementation practice. It is unfortunate that the Standard now contains a clearly incorrect example, but since examples and notes are non-normative this is problematic but not fatal.

like image 198
ecatmur Avatar answered Sep 21 '22 12:09

ecatmur