What Is the restrict Keyword in C and Why Does It Matter?

In C, writing high-performance code often means giving the compiler enough information to make aggressive optimizations. One of the most powerful—and most misunderstood—tools for this is the restrict keyword. Introduced in the C99 standard, restrict is a type qualifier that can be applied to pointer declarations. It tells the compiler that, for the lifetime of that pointer, only that pointer (or a copy of it) will be used to access the object it points to. This seemingly simple promise opens the door to optimizations that would otherwise be impossible or illegal for the compiler to perform.

Without restrict, the C language assumes that any pointer might alias—that is, two or more pointers could refer to the same memory location. This conservative assumption forces the compiler to treat memory accesses as potentially dependent on each other, severely limiting reordering, loop unrolling, and vectorization. By using restrict, you explicitly declare that certain pointers are independent, giving the compiler the freedom it needs to generate faster, more optimized machine code.

The Memory Aliasing Problem

To understand restrict, you first need to grasp the concept of pointer aliasing. In C, if you have two pointer arguments (say, int *a and int *b), the compiler must assume that a and b could point to overlapping memory. Consider this simple function:

void vec_add(int *a, int *b, int *c, int n) {
    for (int i = 0; i < n; i++) {
        a[i] = b[i] + c[i];
    }
}

Because a, b, and c are all unqualified pointers, the compiler cannot be sure that writing to a[i] won’t affect b[i+1] or c[i-1]. In the worst case, a might even point to the same object as b or c. As a result, the compiler is forced to:

  • Read b[i] and c[i] strictly in order
  • Write a[i] immediately, because a later read of b[i+1] might need the updated value if the pointers alias
  • Avoid any reordering that could change the program’s observable behavior under aliasing assumptions

This conservative behavior often prevents the compiler from using SIMD instructions, unrolling the loop, or even keeping values in registers. The result is slower code than what the hardware could theoretically achieve.

How the restrict Keyword Solves the Problem

By qualifying a pointer with restrict, you assert that for the entire lifetime of that pointer, only that pointer (or a pointer derived directly from it, such as by pointer arithmetic) will be used to modify or access the memory it points to. In other words, no other pointer will alias that memory region. This is a contract between the programmer and the compiler; if the contract is violated, the behavior is undefined.

Once the compiler knows that certain pointers are restrict-qualified, it can assume they do not overlap. This allows it to:

  • Reorder read and write operations freely
  • Hold values in registers across loop iterations
  • Use auto-vectorization and SIMD instructions without inserting runtime checks
  • Unroll loops more aggressively
  • Perform constant propagation and dead store elimination that would otherwise be inhibited by possible aliasing

The performance gains can be dramatic, especially in numerical computing, signal processing, and string operations where loops are the dominant cost.

Practical Example: Vector Addition With and Without restrict

Let’s revisit the vector addition example, this time with restrict:

void vec_add_restrict(int *restrict a, int *restrict b, int *restrict c, int n) {
    for (int i = 0; i < n; i++) {
        a[i] = b[i] + c[i];
    }
}

Now the compiler knows that a, b, and c point to three completely disjoint memory regions. It can load several elements from b and c into SIMD registers, add them in parallel, and store the results to a in one go. It can also prefetch the next cache line without worrying that a write to a might invalidate a pending read from b.

To see the real difference, compare the generated assembly (on x86-64 with GCC, using -O2 -fstrict-aliasing). Without restrict, the inner loop typically looks like a scalar load-add-store sequence. With restrict, the compiler often produces vectorized code using instructions such as movdqu, paddd, and movdqa. Benchmarks show speedups of 2× to 4× for small arrays and even more for larger arrays where cache behavior also improves due to reduced aliasing stalls.

Another Classic Example: memcpy vs. memmove

The C standard library functions memcpy and memmove illustrate the practical importance of restrict. The signature of memcpy is:

void *memcpy(void *restrict dest, const void *restrict src, size_t n);

Both dest and src are restrict-qualified because the function requires the source and destination regions not to overlap. This allows the implementation to copy bytes in the most efficient order, such as from low to high addresses word by word. In contrast, memmove does not use restrict because it must handle overlapping regions correctly, which forces a more conservative copy order (e.g., from the end if overlapping). The performance difference between the two functions in safe (non-overlapping) scenarios can be significant.

Where to Use restrict in Real-World Code

While restrict can be applied to any pointer, it is most beneficial in the following contexts:

Numerical Algorithms and Linear Algebra

Functions that perform dense matrix operations, such as BLAS (basic linear algebra subprograms), heavily rely on restrict to achieve peak performance. For example, the SAXPY operation (scalar * x + y) would typically be written as:

void saxpy(int n, float a, float *restrict x, float *restrict y) {
    for (int i = 0; i < n; i++) {
        y[i] = a * x[i] + y[i];
    }
}

Here, restrict tells the compiler that x and y do not overlap, allowing vectorized accumulation without aliasing concerns.

String and Memory Operations

In addition to memcpy, many string functions such as strcpy, strcat, and strncpy use restrict on their destination and source parameters. This enables the compiler to inline these functions and optimize the copy loops aggressively.

Linux Kernel and Embedded Systems

The Linux kernel uses restrict (often via the __restrict__ attribute for GCC compatibility) in critical paths such as networking, block I/O, and device drivers. In embedded systems, where every cycle counts, restrict allows tighter loops and reduced code size.

Important Limitations and Pitfalls

Despite its power, restrict must be used with extreme care. Misusing it leads to undefined behavior, which can manifest as subtle bugs that are difficult to debug.

Undefined Behavior on Aliasing

If you declare a pointer as restrict but then access the memory it points to through another pointer (even a global or a stack variable), the behavior is undefined. For example:

int x;
int *restrict p = &x;
int *q = &x;   // q aliases p's memory
*p = 10;
*q = 20;       // UB: p was restrict-qualified

This rule applies to the entire lifetime of the restrict pointer, from its initialization until it goes out of scope. A common mistake is to pass the same pointer to multiple restrict parameters in a function call. Make sure the memory regions are genuinely disjoint.

No Effect on const or volatile Semantics

restrict interacts with const and volatile but does not change their meaning. A restrict const pointer means the pointer is the only way to access the object and that object cannot be modified through that pointer. A restrict volatile pointer is rare but indicates exclusive, side-effect-bearing access.

Multithreading Limitations

The restrict keyword only applies to pointer aliasing within a single thread. In multithreaded code, even if two threads use restrict pointers, they could still access overlapping memory simultaneously. The C standard does not define behavior for concurrent access; proper synchronization (e.g., mutexes, atomics) is still required. Restrict does not imply any memory ordering or atomicity.

Compiler Differences

While restrict is part of the C99 and later standards, some compilers (especially older ones or those with limited optimization) may ignore it or misinterpret it. In C++, the restrict keyword is not part of the standard; however, many compilers support it as an extension (e.g., __restrict__ in GCC and Clang, __restrict in MSVC). Be sure to check your compiler documentation.

Best Practices for Using restrict

To use restrict effectively and safely, follow these guidelines:

  1. Use restrict only where it clearly applies. Apply it to function parameters that are known to point to non-overlapping memory. Typically this means using it for output pointers (or input pointers when the function does not modify them).
  2. Document the restriction. In comments or documentation, state that the caller must ensure the pointers do not overlap. For example, “The arrays a and b must not overlap.”
  3. Leverage strict aliasing rules. Combine restrict with -fstrict-aliasing (on GCC/Clang) to get maximum benefit. However, be aware that strict aliasing may also be enabled by default at higher optimization levels.
  4. Profile before and after. Measure the performance gain. If the compiler is not generating better code with restrict, you may be misapplying it, or your loop may be memory-bound rather than compute-bound.
  5. Use static analysis tools. Tools like clang-tidy and cppcheck can detect some types of restrict violations. Also consider runtime sanitizers such as -fsanitize=undefined during testing.
  6. Prefer standard library solutions. Where possible, use functions like memcpy that already include restrict, rather than rolling your own pointer-heavy loops.

Compiling with restrict Support

To make full use of restrict, ensure your code is compiled with a C99-or-later standard (e.g., -std=c99, -std=c11, or -std=c17). In C++, use the compiler-specific keyword. For GCC and Clang, you can write:

void foo(int *__restrict__ a, int *__restrict__ b);

This works even in C++ mode. Microsoft Visual C++ uses __restrict. A common pattern for portability is:

#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
   /* C99 or later */
   #define RESTRICT restrict
#elif defined(__GNUC__) || defined(__clang__)
   #define RESTRICT __restrict__
#elif defined(_MSC_VER)
   #define RESTRICT __restrict
#else
   #define RESTRICT
#endif

External Resources and Further Reading

Summary

The restrict keyword is a powerful tool in the C programmer’s optimization toolkit. By telling the compiler that certain pointers are exclusive access paths to their pointed-to memory, you enable transformations that can drastically improve performance—especially in loops, numerical code, and string operations. However, with great power comes great responsibility. Incorrect use of restrict introduces undefined behavior that can be difficult to detect. Use it sparingly, document assumptions clearly, and always verify correctness with profiling and tools. When used correctly, restrict can make the difference between a merely correct program and a blazingly fast one.