Question bank — restrict keyword — aliasing hint
Before we start, a one-line refresher of the vocabulary every question leans on:
The picture behind every question
Aliasing is a statement about byte ranges on the memory line. The first figure is the whole topic in one image — keep it in your head while answering.

The second figure shows why the promise pays off: it turns a strict per-element chain into a reorderable block.

The third figure shows the parameter-reassignment nuance (6.7.3.1 p6): the promise latches onto the value on entry, so overwriting the parameter first can void it.

True or false — justify
Each is a statement. Decide true/false and give the reason — the reason is the whole point. Where code differs, picture the two byte ranges of the previous figure.
restrict guarantees the compiler produces faster code.
int* (a store to a[i] may-alias the load of b[i]), which forbids reordering; restrict downgrades that to "no-alias" (C99 §6.7.3.1 p4). If the loop had no cross-iteration store→load hazard to begin with, the generated code is byte-identical. Speed is a consequence, never a promise.restrict and const do the same job with different spellings.
const (see const qualifier) is a modifiability qualifier: "no write through this pointer." restrict is an aliasing qualifier: "no other, non-derived path reaches this memory" (§6.7.3.1 p4). They are orthogonal; const int * restrict p asserts both at once.If you write restrict and it's a lie, you'll get a compiler error.
-O2/-O3 alias-analysis actually uses the fact.Marking factor as restrict lets the compiler load *factor once, given the full declaration below.
void scale(float * restrict v, float * restrict factor, int n){ for(int i=0;i<n;i++) v[i] *= *factor; }. Without restrict factor, alias analysis must assume factor may point inside v[0..n), so the store v[i] = ... may-alias *factor, forcing a re-load of *factor each iteration. restrict factor proves factor's range is disjoint from v's, so *factor is loop-invariant and hoisted into a register.memcpy is safe on overlapping regions just like memmove.
memcpy(void * restrict s1, const void * restrict s2, size_t n); those restricts make overlap UB. memmove uses no restrict and its spec explicitly permits overlap. See memcpy vs memmove.Two restrict pointers into genuinely disjoint arrays honour the promise even if their addresses are numerically close.
[p, p+len) and [q, q+len) intersect (§6.7.3.1 p4 speaks of the same object). Numerically adjacent but non-overlapping ranges share no byte, so no aliasing.Passing the same array through two restrict parameters is legal as long as you never write through either.
restrict changes the type of the pointer.
int * restrict is a distinct qualified type; but it changes only the aliasing contract, not the pointer's size or representation.A restrict written on a non-pointer declaration is a syntax error.
restrict syntactically on other types but says it has no effect. E.g. int restrict x; is not the intent (compilers usually warn/reject it in that exact spot), but where the grammar allows a qualifier on a non-pointer, it is simply ignored. The rule to remember: restrict only means something on a pointer-to-object.Spot the error
Each snippet has a conceptual flaw (or a deliberate non-flaw). Name it and explain why it bites, tracing the byte ranges from the first figure.
void copy(int * restrict d, const int * restrict s, int n){ for(int i=0;i<n;i++) d[i]=s[i]; }
copy(a+1, a, 3);
``` ::: Error: `d = a+1` covers bytes `[a+1, a+4)` and `s = a` covers `[a, a+3)` — they overlap on `a+1,a+2`. The `restrict` promise is a lie → UB. With vectorization the result is unpredictable (`{a0,a0,a1,a2}` or worse). Use `memmove`.
```c
void f(int * restrict p){ int *q = malloc(4); *q = *p; free(q); }
``` ::: **No error.** At the machine model, `malloc` returns storage whose lifetime *begins* with this call and which has no other live pointer (the standard says the returned object is *disjoint from every other object*, C99 §7.20.3). So `q`'s range cannot equal or overlap `p`'s range — `p` remains the sole path to *its* object. `restrict` forbids other pointers to the **same** memory, not the mere existence of other pointers to *different* memory.
```c
void g(int * restrict p, int * restrict q){ int *r = p; *r = 5; *q = *r; }
``` ::: Conditional error: `r` is *based on* `p` (§6.7.3.1 p4), so `*r = 5` is a write through `p`'s path. If at runtime `q` reaches the same object, the "`p` and `q` disjoint" promise is broken → UB. Deriving `r` from `p` is always fine; aliasing `q` with `p`'s object is the violation.
```c
int x=0; int * restrict p=&x; int * restrict s=&x; *p=1; return *s;
``` ::: Error: `p` and `s` are two **non-derived** `restrict` pointers to the *same* object `x`; we modify through `p` then read through `s`. That is exactly the write-through-one, access-through-another pattern §6.7.3.1 p4 forbids → UB.
```c
void h(float * restrict v, int n){ float t=*v; for(int i=0;i<n;i++) v[i]+=v[0]; }
``` ::: **No error.** `v[0]` and `v[i]` are both accessed through pointers *based on* `v`, so they are the *same* path in the eyes of §6.7.3.1 p4. The promise only bans *other, non-derived* pointers. A `restrict` pointer overlapping *itself* is always allowed.
```c
void k(int * restrict p, int n){ p = p + 1; *p = 0; }
``` ::: **No error, but the nuance matters** (§6.7.3.1 p6): for a parameter, the promise is anchored to the pointer's value *on entry*. Reassigning `p` here still leaves every access *based on* that entry value, so the promise stands. Contrast the next section's edge case where reassigning to an **unrelated** pointer voids it.
---
## Why questions
Why must a non-`restrict` compiler assume `a` might overlap `b` in `a[i]=b[i]+c[i]`? ::: Because C's aliasing model lets any `int*` point at any `int` object; the compiler's alias analysis therefore returns "may-alias" for `a` vs `b`/`c` and must defend against a store to `a[i]` changing a future `b`/`c` load. See [[Compiler optimization & vectorization (SIMD)]].
Why does the aliasing assumption specifically block vectorization? ::: SIMD processes several elements *at once and out of order* (second figure). That is only legal if a store in the batch cannot invalidate a load in the same batch. May-alias forces the strict per-element load→add→store chain, killing the block form.
Why is `restrict` "the programmer's job" rather than the compiler's? ::: Proving two arbitrary pointers never alias across a whole program is undecidable, so §6.7.3.1 offloads the guarantee to the human who knows the call sites, and lets alias analysis *assume* it.
Why does breaking the promise sometimes "work" at `-O0` but fail at `-O2`? ::: `-O0` barely uses alias facts, so it re-reads memory naturally and may accidentally be correct; `-O2` *acts on* the no-alias fact (hoisting, reordering, vectorizing), exposing the lie as wrong output.
Why is copying a `restrict` pointer into a local not a way to "escape" the promise? ::: The copy is *based on* the original (§6.7.3.1 p4), so accesses through it still count as accesses through the original path. The promise follows the *data lineage*, not the variable name.
Why does `restrict` say nothing about thread safety or `volatile`? ::: It is a single-threaded, per-object *aliasing* contract only. Concurrency and hardware-visible side effects are governed by memory models and `volatile`, which are separate mechanisms.
Why can a multi-level pointer make `restrict` confusing? ::: In `int * restrict * p`, the `restrict` qualifies the *inner* pointer type (the `int*` that `*p` yields), not `p` itself; the promise is about the objects those inner pointers reach, so you must track *which* level carries the qualifier before reasoning about disjointness.
---
## Edge cases
What does `restrict` mean when `n == 0` and the loop never runs? ::: Nothing is accessed, so §6.7.3.1 p4 is never triggered; the promise is trivially satisfied even if the ranges overlap. The pointers may even be equal.
Is `T * restrict p = NULL;` legal? ::: Yes to declare — a null `restrict` pointer is fine as long as you never *dereference* it. The promise constrains actual accesses, and a null pointer accesses nothing.
If two `restrict` pointers are equal but you only ever *read* through both, is it UB? ::: No — §6.7.3.1 p4 is only violated when a *modification* through one path coexists with any access through a different, non-derived path. All-reads never modifies, so it is safe.
Does `restrict` on a parameter constrain memory *after* the function returns? ::: No — §6.7.3.1 p6 ties the promise to the function body (the pointer's block). Once the call ends, other pointers may freely reach that memory.
If you reassign a `restrict` **parameter** to an unrelated pointer before the first access, does the promise still bind the new target? ::: Careful — the promise is anchored to the value *on entry* (§6.7.3.1 p6, third figure). Accesses through a pointer *not based on* the entry value are outside the promise, so you can *void* the intended guarantee; conversely accesses through the entry value (and things based on it) remain bound. Rule of thumb: don't reassign restrict parameters if you want the guarantee.
What if a `restrict` pointer's region is one byte adjacent to another's, `q = p + n` exactly? ::: Legal — `p` covers `[p, p+n)`, `q` starts at `p+n`; the ranges touch but share no byte. Disjoint means no shared byte, and there is none.
Can a single `restrict` pointer overlap *itself*, e.g. reading `v[0]` while writing `v[i]`? ::: Yes — both accesses are *based on* the same `restrict` pointer, so they are the same path and §6.7.3.1 p4 does not apply. Self-overlap is always fine.
Two `restrict` pointers to different **members of the same struct** — is that aliasing? ::: No — distinct members occupy distinct, non-overlapping bytes of the struct, so pointers to `&s.a` and `&s.b` reach different objects and honour the promise, even though they live in one struct. (A pointer to the *whole* struct, however, would overlap both.)
Does `restrict` on a non-pointer type do anything? ::: No — §6.7.3.1 p1 says it is *allowed but has no effect* on non-pointer types. Only a pointer-to-object carries the aliasing meaning; anywhere else it is silently a no-op.
---
> [!recall]- One-line summary of the traps
> Almost every trap is one of three lies: (1) confusing `restrict` with `const` (modifiability vs aliasing), (2) thinking the compiler *checks* it (§6.7.3.1 puts it on you), or (3) forgetting the promise covers **based-on** pointers as the *same* path, while only a *different, non-derived* path plus a write causes UB.
## Connections
- [[Pointers in C]]
- [[const qualifier]]
- [[Pointer aliasing]]
- [[memcpy vs memmove]]
- [[Compiler optimization & vectorization (SIMD)]]
- [[Undefined behaviour in C]]
- [[C99 standard features]]