Is (int32_t) 255 << 24 undefined behavior in gcc (C++11)?

Viewed 596

In C++11, according to en.cppreference.com,

For signed and non-negative a, the value of a << b is a * 2b if it is representable in the return type, otherwise the behavior is undefined.

My understanding is that, since 255 * 224 is not representable as an int32_t, the evaluation of (int32_t) 255 << 24 yields undefined behavior. Is that correct? Can this be compiler-dependent? It's an IP16 environment, if that matters.

Background: this comes from an argument I am having with a user at arduino.stackexchange.com. According to him, “there's nothing undefined about that at all”:

you notice that much of the bit shifting is "implementation defined". So you cannot quote chapter-and-verse from the specs. You have to go to the GCC documentation since that is the only place that can tell you what actually happens. gnu.org/software/gnu-c-manual/gnu-c-manual.html#Bit-Shifting - it's only "undefined" for a negative shift value.


Edit: From the answers so far, it would seem my reading of the C++11 standard is correct. Then the key part of my question is whether this expression invokes undefined behavior in gcc. As davmac puts it in his comment, I am asking “whether GCC, an implementation, defines a behaviour even though it is left undefined by the language standard”.

From the gcc manual I linked to, it would seem it is indeed defined, although I find the wording of this manual sounds more like a tutorial than a “language law”. From PSkocik's answer (and Kane's comment to that answer), it would instead seem it is undefined. So I am still in doubt.

I guess my dream would be to have a clear statement in some gcc documentation stating either that 1) gcc does not define any behavior that is explicitly undefined in the standard or, 2) gcc does define this behavior from version XX.XX and commits to keep it defined in all subsequent versions.

Edit 2: PSkocik deleted his answer, which I find unfortunate because it provided interesting information. From his answer, Kane's comment to the answer, and my own experiments:

  1. (int32_t)255<<24 produces a runtime error when compiled with clang and -fsanitize=undefined
  2. the same code produces no error with g++ even with -fsanitize=undefined
  3. (int32_t)256<<24 does give a runtime error when compiled with g++ -std=c++11 -fsanitize=undefined

Point 2 is consistent with the interpretation that gcc, in C++11 mode, defines the left shift more broadly than the standard. As per point 3, this definition could just be the C++14 definition. However, point 3 is inconsistent with the idea that the referenced manual is a complete definition of << in gcc (C++11 mode), as that manual provides no hint that (int32_t)256<<24 could be undefined.

4 Answers

This changed over time, and with good reason, so let's go through the history. Note that in all cases, simply doing static_cast<int>(255u << 24) has always been defined behavior. Maybe just do that and side-step all problems.


The original C++11 wording was:

The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1×2E2, reduced modulo one more than the maximum value representable in the result type. Otherwise, if E1 has a signed type and non-negative value, and E1×2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

255 << 24 is undefined behavior in C++11 because the resulting value is unrepresentable as a 32-bit signed integer, it is too large.

This undefined behavior causes some issues because constexpr must diagnose undefined behavior - and so some common approaches to setting values led to hard errors. Hence CWG 1457:

The current wording of 8.8 [expr.shift] paragraph 2 makes it undefined behavior to create the most-negative integer of a given type by left-shifting a (signed) 1 into the sign bit, even though this is not uncommonly done and works correctly on the majority of (twos-complement) architectures [...] As a result, this technique cannot be used in a constant expression, which will break a significant amount of code.

This was a defect applied against C++11. Technically, a conforming C++11 compiler would implement all of the defect reports, and so it would be correct to say that in C++11, this is not undefined behavior; the behavior for 255 << 24 in C++11 is defined to be -16777216.

The post-defect wording can be seen in C++14:

The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1×2E2, reduced modulo one more than the maximum value representable in the result type. Otherwise, if E1 has a signed type and non-negative value, and E1×2E2 is representable in the corresponding unsigned type of the result type, then that value, converted to the result type, is the resulting value; otherwise, the behavior is undefined.

There were no changes to the wording/behavior in C++17.

But for C++20, as a result of the Signed Integers are Two's Complement (and its wording paper), the wording is greatly simplified:

The value of E1 << E2 is the unique value congruent to E1×2E2 modulo 2N, where N is the range exponent of the type of the result.

255 << 24 still has defined behavior in C++20 (with the same resulting value), it's just that the specification for how we get there becomes a lot simpler because the language doesn't have to work around the fact that the representation for signed integers was implementation-defined.

"Undefined behavior is something for which the standard imposes no requirements." which means it can even be the expected/correct behavior. That C++ standard states with respect to shift operators.

8.5.7 Shift operators [expr.shift] (C++ Standard draft N4713)

  1. The operands shall be of integral or unscoped enumeration type and integral promotions are performed. The type of the result is that of the promoted left operand. The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand. Otherwise, if E1 has a signed type and non-negative value, and E1×2E2 is representable in the corresponding unsigned type of the result type, then that value, converted to the result type, is the resulting value; otherwise, the behavior is undefined.

As @rustyx notes below, "The wording "E1×2E2 is representable in the corresponding unsigned type of the result type" is C++14. Still UB in C++11 unfortunately."

The GNU C compiler defines the left/right shifts fully as described in the manual:

GCC supports only two’s complement integer types, and all bit patterns are ordinary values.

. . .

As an extension to the C language, GCC does not use the latitude given in C99 and C11 only to treat certain aspects of signed ‘<<’ as undefined. However, -fsanitize=shift (and -fsanitize=undefined) will diagnose such cases. They are also diagnosed where constant expressions are required.

So that's consistent with your findings - the code does what you expect, only an overflow beyond the available bits (including the sign bit) will be diagnosed.

As for the GNU C++ compiler, the documentation seems to be really lacking. We can only guess that by omission the shift works in G++ the same way it does in GCC, although at least the sanitizer appears to be aware of the language differences:

-fsanitize=shift

This option enables checking that the result of a shift operation is not undefined. Note that what exactly is considered undefined differs slightly between C and C++, as well as between ISO C90 and C99, etc.

For C++ compilers prior to C++14, if you had a function like this:

// check if the input is still positive,
// after a suspicious shift

bool test(int input)
{
    if (input > 0) 
    {
        input = input << 24;

        // how can this become negative in C++11,
        // unless you are relying on UB?

        return (input > 0); 
    }
    else 
    {
        return false;
    }
}

then an optimizing compiler would have been allowed to change it into this:

bool test(int input)
{
    // unless you are relying on UB, 
    // input << 24 must fit into an int,
    // and input is positive in that branch

    return (input > 0);
}

And everyone is happy because you get a nice speedup in your release builds.

I am, however, not aware of compilers which actually do such optimizations for left-shifts, although it's quite common for additions to be optimized away, as seen in this example.

Related