13

I have the following C listing:

static const int constant = (0 | ((((1 << 6) - 1) << ((((0 + 8) + 8) + 3) + 7)) & ((1) << ((((0 + 8) + 8) + 3) + 7))) | ((((1 << 7) - 1) << (((0 + 8) + 8) + 3)) & ((0) << (((0 + 8) + 8) + 3))) | ((((1 << 3) - 1) << ((0 + 8) + 8)) & ((0) << ((0 + 8) + 8))) | ((((1 << 8) - 1) << 0) & ((1) << 0)));

int main(int argc, char** argv)
{
    return constant;
}

When I am trying to compile this with GCC-9.1 using this command line:

gcc-9 -Werror -Wpedantic main.c

I am getting this error:

main.c:1:29: error: initializer element is not a constant expression [-Werror=pedantic]

Why is that? Is this a compiler bug? Clearly, constant is initialized with a constant expression.

ivaigult
  • 6,198
  • 5
  • 38
  • 66
  • Have a look: https://stackoverflow.com/questions/3025050/error-initializer-element-is-not-constant-when-trying-to-initialize-variable-w/3025106 – PM 77-1 Jul 02 '19 at 15:44
  • 2
    @PM77-1: That's not the issue. The question isn't whether the expression `constant` is a constant expression, it's whether its initializer is a constant expression. – Keith Thompson Jul 02 '19 at 15:46
  • 1
    Could it be that some subexpression overflows the range of `int`? (Note that clang doesn't complain.) Could be a gcc bug. – Keith Thompson Jul 02 '19 at 15:47
  • 1
    @vaigult I can' not reproduce the error. – Vlad from Moscow Jul 02 '19 at 15:48
  • @VladfromMoscow it reproduces only on gcc-9.1, which I installed from `ppa:ubuntu-toolchain-r/test`. – ivaigult Jul 02 '19 at 15:50
  • Out of curiosity... where did this constant expression come from? Were you trying to break the compiler? – Christian Gibbons Jul 02 '19 at 15:54
  • 1
    @ChristianGibbons the issue looked way more complicated initially. I extracted this expression out of a huge `#define` macro evaluation by using `gcc -E`. – ivaigult Jul 02 '19 at 15:57
  • "it reproduces only on gcc-9.1", if true, is a strong reason to suspect a compiler bug. I can confirm that my GCC 4.8.5 accepts the initializer without any complaint, and as far as I can tell from personal examination, the troublesome initializer does have the form of an integer constant expression. – John Bollinger Jul 02 '19 at 16:04
  • No problem with Visual C. – Weather Vane Jul 02 '19 at 16:18
  • 4
    You have UB as a result of an overflowing left-shift, so this example does not demonstrate a compiler bug. I'm not sure whether that fully explains the issue, however. Does GCC 9.1 still reject the code if you change the `(1 << 6)` to `(1 << 5)`? – John Bollinger Jul 02 '19 at 16:40
  • Using GCC 9.1.0 which I compiled on macOS 10.14.5 Mojave, I get no error report with GCC set fussy (`gcc -O3 -g -std=c11 -Wall -Wextra -Werror const23.c -o const23`), though I did replace `main` with `int main(void)` to avoid complaints about unused arguments. I observe that `0` bitwise or'd with anything is the same as 'anything' — but that doesn't affect the discussion. —— **BUT** if I add `-pedantic` to the options, then I get the error message. I think it would probably be pedantically correct to assume this is erroneous. – Jonathan Leffler Jul 02 '19 at 16:43
  • 3
    @JohnBollinger: For me, if I change the first (only) `(1 << 6)` to `(1 << 5)`, the error message goes away. The term becomes `((((1 << 5) - 1) << ((((0 + 8) + 8) + 3) + 7)) & ((1) << ((((0 + 8) + 8) + 3) + 7)))` which avoids shifting `1` left by 32 (the left shifting in total is 31) when you change 6 to 5. That's probably a mix of 'good catch' and 'poor message' by the compiler. – Jonathan Leffler Jul 02 '19 at 16:52
  • Changing `(1 << 6)` to `(1u << 6)` also makes the error message go away. – Ian Abbott Jul 02 '19 at 17:00
  • I can reproduce the issue with all versions of gcc I have available, from 6.3.0 up to a pre-10.0.0 built from source. – Keith Thompson Jul 02 '19 at 18:22

1 Answers1

14

I am getting this error:

main.c:1:29: error: initializer element is not a constant expression [-Werror=pedantic]

Why is that? Is this a compiler bug? Clearly, constant is initialized with a constant expression.

"Constant expression" is a defined term in the language standard. I suspect that GCC is using it that way, as the standard does require your initializer to be a constant expression in that sense. Certainly the evaluation of your code needs to be performed in that light.

There are two language constraints on constant expressions:

Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.

and

Each constant expression shall evaluate to a constant that is in the range of representable values for its type.

The former is not a problem for you. The latter, however, is an issue on C implementations where type int has 31 or fewer value bits (including GCC on most platforms). Consider in particular this sub-expression:

(((1 << 6) - 1) << ((((0 + 8) + 8) + 3) + 7))

... but for sanity, let's remove some unnecessary parentheses and simplify the right-hand side of the outer << to get this, which preserves the relevant characteristics:

((1 << 6) - 1) << 26

All the individual numeric constants have type int, therefore so also do all the intermediate results (where the "26" in the simplified version corresponds to such an intermediate result in the original expression). The arithmetically-correct result for that left shift requires at least 32 value bits, and because your int (probably) doesn't have that many, since one bit is reserved for the sign, the behavior is undefined.

As such, there is no compiler bug here, though you might have a grounds for a quality of implementation complaint. Likewise, no compiler that accepts the code without warning or error is for that reason buggy. In a different sense, your code does violate a language constraint, and in that sense the compiler is obligated to emit a diagnostic, although the one it has chosen seems misleading.

Furthermore, others' comments on the question seem to confirm that the overflow is correlated with the error, since changing the called-out expression from using (1 << 6) to either (1 << 5) or (1u << 6) resolves the error for others who could reproduce it. Both yield overall expression without any sub-expressions that have undefined behavior.

Note well that it is almost always better to avoid signed integer types when you are doing bitwise manipulation. As such, neglecting any impact on the larger program from which this was drawn, I would be inclined to rewrite your example program as

static const unsigned int constant = (0 
    | ((((1u << 6) - 1) << ((((0 + 8) + 8) + 3) + 7)) & ((1u) << ((((0 + 8) + 8) + 3) + 7)))
    | ((((1u << 7) - 1) << (((0 + 8) + 8) + 3))       & ((0u) << (((0 + 8) + 8) + 3)))
    | ((((1u << 3) - 1) << ((0 + 8) + 8))             & ((0u) << ((0 + 8) + 8)))
    | ((((1u << 8) - 1) << 0)                         & ((1u) << 0)));

int main(void) {
    // There's a potential issue with the conversion of the return value, too, but it
    // does not affect the particular expression at issue here.
    return constant;
}

Note that the type of the result of a bitwise shift is determined by the type of its left-hand operand only.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Tl;dr Why does an expression that would produce UB affect the compiler output? Is that a part of UB cases? – machine_1 Jul 02 '19 at 18:06
  • The error message is correct, but it could be improved by explaining *why* the expression is not a constant expression (in this case, that a subexpression overflows). – Keith Thompson Jul 02 '19 at 18:30
  • @machine_1, in general, "undefined behavior" applies to the combination of program and C implementation. It can manifest either at translation (compile) time or at run time. Certain undefined behaviors make sense only at run time -- for instance, if they are contingent on externally-provided data -- but this is not such a case. – John Bollinger Jul 02 '19 at 18:42