Code of the Day
AdvancedAdvanced pointers and memory

restrict and aliasing rules

Understand C's strict aliasing rules, why the compiler makes aliasing assumptions during optimisation, and how restrict enables better code generation.

CAdvanced10 min read
By the end of this lesson you will be able to:
  • Explain what pointer aliasing is and why it constrains compiler optimisation
  • Describe the strict aliasing rule and which types can alias
  • Use restrict to inform the compiler that pointers do not alias
  • Recognise code that violates strict aliasing

One of the most subtle aspects of C's memory model is aliasing: when two pointers refer to the same memory. The C standard restricts aliasing in ways that let the compiler generate much faster code — but violating these rules is undefined behaviour, and the consequences can be silently wrong results after compiler optimisation.

What is aliasing?

Two pointers alias when they point to the same (or overlapping) memory:

int x = 10;
int *p = &x;
int *q = &x; /* p and q alias each other */
*p = 20;
printf("%d\n", *q); /* 20 -- q sees the change */

Aliasing is expected in this case. The problem arises when the compiler assumes pointers do not alias, permitting optimisations that break the code if they do.

The strict aliasing rule

The C standard says: an object may only be accessed through an lvalue of a compatible type (or char *). Accessing an int through a float * is undefined behaviour — the strict aliasing rule.

int i = 42;
float *fp = (float *)&i;
printf("%f\n", *fp); /* UNDEFINED BEHAVIOUR: accessing int through float * */

This rule permits the compiler to assume that a float * and an int * do not point to the same memory. It can then reorder loads and stores freely, keeping values in registers without reloading from memory.

The compiler is not wrong to do this. The rule exists to enable legitimate, important optimisations. The programmer is wrong to violate it.

Type-punning correctly: memcpy or union

When you genuinely need to reinterpret bytes (common in network code and serialisation):

/* WRONG: violates strict aliasing */
float f = 3.14f;
uint32_t bits = *(uint32_t *)&f;

/* CORRECT: use memcpy */
float f = 3.14f;
uint32_t bits;
memcpy(&bits, &f, sizeof(float));
printf("0x%X\n", bits); /* correct bit representation */

memcpy is the portable, defined way to type-pun. An optimising compiler will compile memcpy between same-sized types to a single register move.

Alternatively, C99 allows type-punning via a union:

union FloatInt {
    float    f;
    uint32_t i;
};

union FloatInt pun;
pun.f = 3.14f;
printf("0x%X\n", pun.i); /* reading a different union member -- defined in C99 */

The restrict keyword

restrict is a promise to the compiler: "for the lifetime of this pointer, no other pointer will be used to access the object it points to." This allows better code generation for pointer-heavy functions.

Without restrict, the compiler must assume dst and src might alias in this copy loop:

void copy(int *dst, const int *src, int n) {
    for (int i = 0; i < n; i++) {
        dst[i] = src[i];
    }
    /* Compiler: src might point into dst, must reload src[i] each iteration */
}

With restrict, it can vectorise the loop:

void copy(int * restrict dst, const int * restrict src, int n) {
    for (int i = 0; i < n; i++) {
        dst[i] = src[i];
    }
    /* Compiler: guaranteed no aliasing, can use SIMD */
}

The standard library's memcpy uses restrict; that is why memmove exists for overlapping regions.

Lying to restrict is undefined behaviour. If you call a restrict-annotated function with aliasing pointers, the compiler is allowed to generate broken code and you have no recourse. Only use restrict when you can guarantee non-aliasing at every call site.

GCC's -fno-strict-aliasing flag disables the aliasing assumptions. This is a common workaround in legacy codebases that violate strict aliasing, but it disables a class of important optimisations. Fix the code instead.

Where to go next

Next: writing a simple allocator — applying everything about memory layout to build a rudimentary malloc implementation using a free list.

Finished reading? Mark it complete to track your progress.

On this page