Why Signed 32-Bit Integer Left Shift by 31 Causes UB

In systems programming languages like C and C++, shifting a signed 32-bit integer left by 31 positions (1 << 31) triggers undefined behavior because the operation attempts to produce a value that exceeds the maximum representable positive value of the signed type. A 32-bit signed integer reserves its most significant bit as the sign bit, leaving only 31 bits for the magnitude of non-negative values. Shifting a positive value into the sign bit causes an arithmetic overflow according to the language specifications, leading standard compilers to treat the operation as undefined.

The Bit Layout of a Signed 32-Bit Integer

A standard signed 32-bit integer (such as int32_t or a typical 32-bit int) consists of 32 binary digits: * Bits 0 to 30: 31 magnitude (value) bits. * Bit 31 (MSB): 1 sign bit, where 0 indicates a positive number and 1 indicates a negative number in two’s complement representation.

Because bit 31 is designated for the sign, the maximum positive value that can be represented is \(2^{31} - 1\), which equals \(2,147,483,647\). The minimum negative value representable in two’s complement is \(-2^{31}\), which equals \(-2,147,483,648\).

Mathematical Definition of the Left Shift

In the C and C++ language standards, the left-shift operator E1 << E2 on a signed integer is defined mathematically as:

\[\text{Result} = E1 \times 2^{E2}\]

When evaluating 1 << 31: 1. The operand 1 is multiplied by \(2^{31}\). 2. The mathematical result is \(2,147,483,648\). 3. This value (\(2,147,483,648\)) is strictly greater than the maximum representable signed positive integer (\(2,147,483,647\)).

Because the true mathematical product cannot fit inside the positive range of a signed 32-bit integer, an arithmetic overflow occurs.

Language Specifications and Undefined Behavior

According to ISO C standards (such as C99 and C11 §6.5.7), if the operand E1 has a signed type and non-negative value, and \(E1 \times 2^{E2}\) is not representable in the result type, the behavior is explicitly undefined.

This rule was established for several reasons: * Hardware Portability: Early computing architectures did not universally use two’s complement arithmetic. Ones’ complement and sign-magnitude systems handle bit 31 differently, making bit-shifting directly into the sign bit non-portable. * Compiler Optimizations: Defining signed overflow as undefined behavior allows optimizing compilers to assume signed integers will never overflow. This enables aggressive loop vectorization, constant folding, and dead code elimination.

Modern Standards and Safe Alternatives

While C++20 formally adopted two’s complement arithmetic and defined 1 << 31 as yielding the signed value \(-2,147,483,648\), the operation remains undefined behavior in standard C (up through C17) and pre-C++20 standards.

To safely shift 31 bits without invoking undefined behavior, the shift must be performed using an unsigned 32-bit integer, where bit 31 is part of the standard numeric range:

// Undefined Behavior in C99/C11/C17:
int32_t a = 1 << 31;

// Defined and Safe:
uint32_t b = 1U << 31;
int32_t c = (int32_t)(1U << 31); // Explicit cast to signed

Using 1U << 31 ensures that the calculation operates within the unsigned range of \(0\) to \(4,294,967,295\), preventing signed arithmetic overflow.