Strict Aliasing Rule and Pointer Casting in C++
The strict aliasing rule is a compiler optimization guideline in C++ specifying that two pointers of different types cannot point to the same memory location, with a few precise exceptions. Violating this rule through unsafe pointer typecasting results in undefined behavior, often manifesting as subtle, hard-to-debug runtime bugs when compiler optimizations are enabled. Understanding this rule is essential for writing correct, modern C++ code, particularly when performing low-level memory manipulation or type punning.
What Is Aliasing and the Strict Aliasing Rule?
In C++, aliasing occurs when multiple pointers or references refer to the exact same location in memory. When a compiler generates machine code, it must track whether an operation modifying memory through one pointer affects reads or writes through another pointer.
To maximize performance, modern C++ compilers assume the strict aliasing rule: pointers of fundamentally distinct types do not point to the same memory address. This assumption allows the compiler to cache values in CPU registers, reorder read and write instructions, and eliminate redundant memory loads.
For example, consider a function that accepts an int*
and a float*:
void compute(int* a, float* b) {
*a = 5;
*b = 3.14f;
// The compiler assumes modifying *b does not affect *a.
// It can reuse the cached value 5 instead of re-reading memory for *a.
}Because of strict aliasing, the compiler is guaranteed that modifying
*b cannot alter *a.
Permitted Exceptions to the Rule
The C++ standard defines a small set of exceptions where aliasing between different types is explicitly allowed:
- Character Types: Pointers to
char,unsigned char, andstd::byteare allowed to alias any other type. They are treated as raw byte views of memory. - Signedness: A signed type and its unsigned variant
(e.g.,
intandunsigned int) may alias each other. - Const/Volatile Variations: A type with or without
cv-qualifiers (e.g.,
intandconst int) may alias. - Inheritance: A pointer to a base class may alias an instance of an object derived from that base class.
How Strict Aliasing Impacts Pointer Typecasting
Developers commonly encounter strict aliasing violations when
attempting to inspect the binary representation of an object—a technique
known as type punning. Using reinterpret_cast or C-style
casts to cast a pointer from one type to an incompatible type violates
the strict aliasing rule.
// INCORRECT: Strict aliasing violation
float f = 5.0f;
int* i = reinterpret_cast<int*>(&f); // Incompatible types
int bits = *i; // Undefined Behavior!In the example above, the compiler is free to assume f
and *i cannot reference the same memory. As a result,
optimization passes might hoist the read of *i before the
initialization of f, read stale values from a register, or
discard writes entirely. While the code might appear to work under
-O0 (optimizations disabled), it frequently breaks under
-O2 or -O3.
Safe Alternatives to Pointer Typecasting
Instead of casting pointers directly, C++ provides safe and standard-compliant methods to inspect or convert raw bit representations without violating strict aliasing.
1. std::memcpy
Using std::memcpy copies the raw bytes of an object into
another. Modern compilers recognize this pattern and optimize the call
away entirely, emitting a simple register move instruction instead of an
actual library call.
#include <cstring>
float f = 5.0f;
int bits;
std::memcpy(&bits, &f, sizeof(bits)); // Safe and optimized2. std::bit_cast (C++20)
C++20 introduced std::bit_cast in the
<bit> header, which formalizes type punning cleanly.
It enforces equal sizes and trivially copyable types at compile time,
and it can be evaluated in constexpr contexts.
#include <bit>
float f = 5.0f;
int bits = std::bit_cast<int>(f); // Safe, standard C++203. Character Pointers
Reading the memory through a character pointer (char*,
unsigned char*, or std::byte*) is explicitly
allowed:
float f = 5.0f;
auto* byte_ptr = reinterpret_cast<unsigned char*>(&f);
// Valid: char-like types are allowed to alias any object
unsigned char first_byte = byte_ptr[0]; Direct pointer casts between incompatible non-character types bypass
compiler safety checks and violate the C++ memory model. To prevent
optimization-induced bugs, developers should rely on
std::bit_cast or std::memcpy rather than
casting pointers to inspect or reinterpret underlying bits.