"initialiser element is not constant" error in C, when using static const variable - Sometimes - Compiler settings?

Viewed 224

I'm posting this because I couldn't find a suitable answer elsewhere, not because similar things haven't been asked before.

A project compiles just fine with the following:

#include <stdint.h>
void foo(void)
{  if (bar)
   {  static const uint8_t ConstThing = 20;
      static uint8_t StaticThing = ConstThing;
        //...
    }
}

But a cloned project does not, throwing the above error. Looks like we've not completely cloned compiler settings / warning levels etc, but can't find the difference right now.

Using arm-none-eabi-gcc (4.7.3) with -std=gnu99. Compiling for Kinetis.

If anyone knows which settings control cases when this is legal and illegal in the same compiler, I'm all ears. Thanks in advance.

2 Answers

Found the difference.

  • If optimisation is -O0 it doesn't compile.
  • If optimisation is -OS it does.

I'm guessing it produces 'what you were asking for, a better way' and fixes it.

Didn't see that coming. Thanks for your input everyone.

Converting some of my comments into an answer.

In standard C, ConstThing is a constant integer, but not an integer constant, and you can only initialize static variables with integer constants. The rules in C++ are different, as befits a different language.

C11 §6.7.9 Initialization ¶4 states:

All the expressions in an initializer for an object that has static or thread storage duration shall be constant expressions or string literals.

§6.4.4.1 Integer constants defines integer constants.

§6.6 Constant expressions defines constant expressions.

…I'm not sure I understand the difference between a 'constant integer' and an 'integer constant'.

Note that ConstThing is not one of the integer constants defined in §6.4.4.1 — so, whatever else it is, it is not an integer constant. Since it is a const-qualified int, it is a constant integer, but that is not the same as an integer constant. Sometimes, the language of the standard is surprising, but it is usually very precise.

The code in the question was compiled by GCC 4.7.3, and apparently compiling with -O0 triggers the error and compiling with -Os (-OS is claimed in the question, but not supported in standard GCC — it requires the optional argument to -O to be a non-negative integer, or s, g or fast) does not. Getting different views on the validity of the code depending on the optimization level is not a comfortable experience — changing the optimization should not change the meaning of the code.

So, the result is compiler dependent — and not required by the C standard. As long as you know that you are limiting portability (in theory, even if not in practice), then that's OK. It's if you don't realize that you're breaking the standard rules and if portability matters, then you have problems of the "Don't Do It" variety.' Personally, I wouldn't risk it — code should compile with or without optimization, and should not depend on a specific optimization flag. It's too fragile otherwise.

Having said that, if it's any consolation, GCC 10.2.0 and Apple clang version 11.0.0 (clang-1100.0.33.17) both accept the code with options

gcc -std=c11 -pedantic-errors -pedantic -Werror -Wall -Wextra -O3 -c const73.c

with any of -O0, -O1, -O2, -O3, -Os, -Og, -Ofast. That surprises me — I don't think it should be accepted in pedantic (strictly) standard-conforming mode (it would be different with -std=gnu11; then extensions are deemed valid). Even adding -Weverything to the clang compilations does not trigger an error. That really does surprise me. The options are intended to diagnose extensions over the standard, but are not completely successful. Note that GCC 4.7.3 is quite old; it was released 2013-04-11. Also, GCC 7.2.0 and v7.3.0 complain about the code under -O0, but not under -Os, -O1, -O2, or -O3 etc, while GCC 8.x.0, 9.x.0 and 10.x.0 do not.

extern int bar;
extern int baz;
extern void foo(void);

#include <stdio.h>
#include <stdint.h>

void foo(void)
{
    if (bar)
    {
        static const uint8_t ConstThing = 20;
        static uint8_t StaticThing = ConstThing;
        baz = StaticThing++;
    }
    if (baz)
        printf("Got a non-zero baz (%d)\n", baz);
}

However, I suspect that you get away with it because of the limited scope of ConstThing. (See also the comment by dxiv.)

If you use extern const uint8_t ConstThing; (at file scope, or inside the function) with the initializer value omitted, you get the warning that started the question.

extern int bar;
extern int baz;
extern void foo(void);

#include <stdio.h>
#include <stdint.h>

extern const uint8_t ConstThing; // = 20;

void foo(void)
{
    if (bar)
    {
        static uint8_t StaticThing = ConstThing;
        baz = StaticThing++;
    }
    if (baz)
        printf("Got a non-zero baz (%d)\n", baz);
}

None of the compilers accepts this at any optimization level.

Related