Home > Software design >  Contradictory definitions about the Order of Constant Initialization and Zero Initialization in C
Contradictory definitions about the Order of Constant Initialization and Zero Initialization in C

Time:09-22

I have been trying to understand how static variables are initialized. And noted a contradiction about the order of constant initialization and zero initialization at cppref and enseignement.

At cppref it says:

Constant initialization is performed instead of zero initialization of the static and thread-local (since C 11) objects and before all other initialization.

Whereas at enseignement it says:

Constant initialization is performed after zero initialization of the static and thread-local objects and before all other initialization.

So as you can see cppref uses "instead" while the second site uses "after". Which of the two is correct? That is, does zero initialization always happen first and then if possible constant initialization as implied by the second site or the other way round.

The example given there is as follows:

#include <iostream>
#include <array>
 
struct S {
    static const int c;
};
const int d = 10 * S::c; // not a constant expression: S::c has no preceding
                         // initializer, this initialization happens after const
const int S::c = 5;      // constant initialization, guaranteed to happen first
int main()
{
    std::cout << "d = " << d << '\n';
    std::array<int, S::c> a1; // OK: S::c is a constant expression
//  std::array<int, d> a2;    // error: d is not a constant expression
}

This is my understanding of the initialization process so far:

  1. Static initialization happens first. This includes
  • Constant initialization if possible
  • Zero initialization only if constant initialization was not done
  1. Dynamic Initialization

Now according to the above(my understanding) this is how the code above works:

Step 1.

When the control flow reaches the definition of const int d it sees that the initializer has a variable(namely S::c) that has not been already initialized. So the statement const int d = 10 * S::c; is a dynamic time(runtime) initialization. This means it can only happen after static initialization. And ofcourse d is not a constant expression.

Step 2.

The control flow reaches the definition of variable const int S::c; . In this case however the initializer is a constant expression and so constant initialization can happen. And there is no need for zero initialization.

Step 3.

But note that we(the compiler) still haven't initialized the variable d because it left its initialization because it has to be done dynamically. So now this will take place and d will get value 50. But note d still isn't a constant expression and hence we cannot use it where a constant expression is required.

Is my analysis/understanding of the concept correct and the code behaves as described?

Note:

The order of constant initialization and zero initialization is also different at cppref-init and enseignement-init.

CodePudding user response:

When in doubt, turn to the standard. As this question is tagged with C 11, we'll refer to N3337.

[basic.start.init]/2:

Variables with static storage duration ([basic.stc.static]) or thread storage duration ([basic.stc.thread]) shall be zero-initialized ([dcl.init]) before any other initialization takes place.

Constant initialization is performed: [...]

Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.

Thus, with regard to the C 11 Standard, enseignement's description is accurate.

Constant initialization is performed after zero initialization of the static and thread-local objects and before all other initialization.

However, this was flagged as a defect as per CWG 2026:

CWG agreed that constant initialization should be considered as happening instead of zero initialization in these cases, making the declarations ill-formed.

And as of C 17 (N4659) this was changed, and henceforth governed by [basic.start.static]/2:

[...] Constant initialization is performed if a variable or temporary object with static or thread storage duration is initialized by a constant initializer for the entity. If constant initialization is not performed, a variable with static storage duration or thread storage duration is zero-initialized. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization.

But as this was not just new standard feature update, but a defect, it backports to older standard, and in the end cppreference's description is accurate also for C 11 (in fact all the way back to C 98), whereas enseignement's has not taken into account neither modern C standard nor the DR CWG 2026.

I have never visited enseignement's page myself, but after a quick glance at their reference page, it looks like a really old version of cppreference itself, either entirely unattributed to cppreference, or cppreference actually started as a collab with the enseignement authors.

Short of the standard itself I consider cppreference to be a de facto reference, and given the old copy-pasta of enseignement I would recommend never to turn to those for pages reference again. And indeed, we may visit the actual up-to-date cppreference page on constant initialization, scroll to the very bottom and read:

Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C standards.

[...]

  • CWG 2026
    • Applied to: C 98
    • Behavior as published: zero-init was specified to always occur first, even before constant-init
    • Correct behavior: no zero-init if constant init applies
  • Related