Initializers for objects with static storage duration are required to be composed of constant expressions. As @EugenSh. observed in comments, "constant expression" is a defined term. Specifically, in C2011 it is the subject of section 6.6. The description is simply
A constant expression can be evaluated during translation rather than
runtime, and accordingly may be used in any place that a constant may
be.
But the devil is in the details. The semantic details of constant expressions contain specific rules for particular kinds and uses of constant expressions.
For example, the expression a
is not an "integer constant expression" under any circumstance, regardless of the type or const
ness of a
, and therefore may not be used where the standard requires that specific kind of constant expression, such as in bitfield widths.
Although the standard does not give a name to it, it presents slightly more relaxed rules for constant expressions in initializers, which is the case we're considering here:
Such a constant expression shall be, or evaluate to, one of the
following:
- an arithmetic constant expression,
- a null pointer constant,
- an address constant, or
- an address constant for a complete object type plus or minus an integer constant expression.
The terms "arithmetic constant expression" and "address constant" are also defined:
An arithmetic constant expression shall have arithmetic type and shall
only have operands that are integer constants, floating constants,
enumeration constants, character constants, sizeof expressions whose
results are integer constants, and _Alignof
expressions. [...]
An address constant is a null pointer, a pointer to an lvalue
designating an object of static storage duration, or a pointer to a
function designator; it shall be created explicitly using the unary &
operator or an integer constant cast to pointer type, or implicitly by
the use of an expression of array or function type. [...]
None of the initializers for your various b
variables conform to those rules. An lvalue expression designating an object having const
-qualified type is not among the elements that are permitted to appear in any of the varieties of constant expression that the standard requires for initializers.
The standard does allow generally that
An implementation may accept other forms of constant expressions.
, but that does not override its specific requirements for constant expressions appearing in initializers.
Each of the given declarations of a variable b
violates a "shall" requirement of the standard appearing outside a constraint. The resulting behavior is therefore undefined, but the standard does not require a diagnostic. Implementations may accept such forms as an extension, as GCC 8.2 evidently does, and GCC's -pedantic
option ensures diagnostics only where required by the standard, which does not include these cases.
Since the behavior is undefined, none of the observed behavior of various implementations is non-conforming. You cannot rely on a conforming implementation to reject non-conforming code. Under some circumstances (but not these) it must diagnose non-conformance, but even in such cases it is permitted to translate successfully anyway.
I am lost. Is it expected behavior and such code should compile?
No, but neither is it safe to expect that it should fail to compile.
Why/Why not?
I explain above why the various codes fail to conform, and therefore may by rejected by conforming compilers. But on the other side, conforming compilers are not required to reject non-conforming code.
What changed between gcc7.4 and gcc8.1? Is this a compiler bug? Is this a compiler extension?
GCC evidently implemented an extension. I'm uncertain whether that was intentional, but it's certainly the result. As long as the behavior is what one would naively expect, it seems pretty natural and benign, except from the point of view of GCC (not) helping you to write conforming code.