17
static const int a = 42;
static const int b = a;

I would expect a compilation error in such code. The initializer must be a constant expression or a string literal. A value stored in an object with the type int with const type qualifier is not a constant expression.

I compile with -Wall -Wextra -pedantic, even with -ansi. Then:

Surprisingly, the following:

static const char * const a = "a";
static const char * const b = a;

For the snipped below, I think I was 100% sure that it should not compile:

static const int a[] = { 1, 2, 3 };
static const int b = a[1];

, but:

I tried browsing the net for an explanation, which mostly resulted in copying the code out of old stackoverflow questions about not constant intializers and finding out if they work now. I couldn't find anything relevant in gcc 8 changes.

I am lost. Is it expected behavior and such code should compile? Why/Why not? What changed between gcc7.4 and gcc8.1? Is this a compiler bug? Is this a compiler extension?

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • I think, the older versions of the compiler simply fail to detect, that the initializer expression _indeed_ is constant. – Ctx Jan 10 '19 at 19:50
  • 4
    As per [standard 6.6 - constant expressions](http://port70.net/~nsz/c/c11/n1570.html#6.6) - constant expression is anything that can be evaluated at translation phase. Which looks like it is making it implementation defined - it's up to the compiler to decide what it can evaluate and what it can't. – Eugene Sh. Jan 10 '19 at 19:51
  • @EugeneSh. And the wording for [C89](http://port70.net/~nsz/c//c89/c89-draft.html) is similar: "A constant expression can be evaluated during translation rather than runtime, and accordingly may be used in any place that a constant may be." – Andrew Henle Jan 10 '19 at 19:57
  • I guess the culprit is [6.6p10](http://port70.net/~nsz/c/c11/n1570.html#6.6p10) - *An implementation may accept other forms of constant expressions.* – Eugene Sh. Jan 10 '19 at 20:32
  • Yes but no, @EugeneSh.. Although an implementation may *generally* accept other forms of constant expression, the forms that are permitted *in initializers* are laid out specifically in 6.6/6. – John Bollinger Jan 10 '19 at 20:38
  • 1
    @AnT Is this a duplicate? I don't ask why does the error “initializer element is not constant” occur, I ask why the gcc implementation with version higher then 8.1 does _not_ produce the error. And if is it a conforming implementation if it doesn't produce the error. Is not producing such error an extension or isn't? The linked duplicate will still be errored by the compiler - gcc seems not to allow initializing structures from const qualived structs. – KamilCuk Jan 10 '19 at 20:48
  • 1
    @Kamil Cuk: No, it is not an appropriate duplicate, since this question's focus is different. I revoked my vote. – AnT stands with Russia Jan 10 '19 at 20:48
  • @Kamil Cuk Recommend to post here the _exact_ failure messages. Makes the question easier to find and thus more useful. – chux - Reinstate Monica Jan 10 '19 at 21:05
  • Perhaps add [tag:language-lawyer] tag? – Nominal Animal Jan 10 '19 at 21:08
  • I had already considered it, @NominalAnimal, and since you now suggest it as well... done. – John Bollinger Jan 10 '19 at 21:12

1 Answers1

12

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 constness 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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 1
    The standard says, as you quote, that a initializer "shall be, **or evaluate to**," ... an arithmetic constant expression. So it doesn't have to have the precise *form* of an arithmetic constant expression. I think that's part of the extra latitude. – rici Jan 10 '19 at 21:00
  • @rici, expressions evaluate to *values* not, generally, other expressions. I think the "or evaluate to" language therefore needs to be read very narrowly. In particular, I see no scope for it outside of allowing string literals and `&` expressions as constant expressions, subject to the rules for address constants. – John Bollinger Jan 10 '19 at 21:04
  • FWIW: `static const int c = (int){a};` is also "error: initializer element is not constant". – chux - Reinstate Monica Jan 10 '19 at 21:04
  • 1
    @john: well, feel free to file a bug, then. :) I think GCC is entirely within its rights to accept static int const variables in that context. – rici Jan 10 '19 at 21:07
  • 1
    @rici, I'm not inclined to file a bug report, for, as I said, there is no bug here. ALL of the behaviors described in the question are conforming. It just happens that GCC 8.2's behavior in this area constitutes an extension. – John Bollinger Jan 10 '19 at 21:08
  • `-pedantic` does not “[ensure] diagnostics only where required by the standard”. Per [the GCC documentation](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html), it causes the rejection of certain extensions, so it turns some diagnostics on, not off, and it causes GCC to show some diagnostics “for which ISO C requires a diagnostic, and some others for which diagnostics have been added.” – Eric Postpischil Jan 10 '19 at 21:49
  • @EricPostpischil, `-pedantic` (ensures diagnostics) (only where [they are] required by the standard). So yes, it turns warnings on, not off, and I never said otherwise. The point is that it does not promise to warn about conformance issue for which the standard does not require a diagnostic, such as we're discussing. – John Bollinger Jan 10 '19 at 22:48
  • `-pedantic` does not “[ensure] diagnostics **only** where required by the standard”. It enables diagnostics where required by the standard, and it enables some others. – Eric Postpischil Jan 10 '19 at 23:37
  • Are we really doing this, @EricPostpischil? Fine. GCC's general definition of the effect of `-pedantic`, which you quoted, does not promise any specific diagnostics other than those required by the standard. So it *ensures* diagnostics only where they are required by the standard. My statement does not speak either way to whether `-pedantic` turns on any additional warnings, and it did not need to do for my purposes. – John Bollinger Jan 10 '19 at 23:45
  • I think this was introduced as an extension with GCC 8.1, here is a bug report where it was discussed: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69960 . This extension brings the behavior a bit closer to C++. – jdm Nov 29 '20 at 17:54
  • @John Bollinger - Regarding _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._ I'm not convinced of this interpretation as an _override_, since it is not at all clear (at least to me) that §10 should be in contrast to §8 and §9 not a continuation of §7. If it really weren't meant to also refer to _constant expressions in initializers_, where else should these _other forms of constant expressions_ be accepted? – Armali Apr 08 '21 at 17:30
  • 1
    @Armali, in the end, it does not matter to the conclusion that GCC 8 has implemented an extension. Paragraph 6.6.10 is largely meaningless, in that removing it from the standard would not make it untrue. With it or without it, an implementation can accept forms of constant expression other than specifically permitted by the standard, and with it or without it, doing so constitutes an extension. – John Bollinger Apr 08 '21 at 18:05