5.1.32 · D3 · Coding › C Programming › restrict keyword — aliasing hint
Parent note ne bataya tha ki restrict ek promise hai: "is pointer ke peeche jo memory hai, wo sirf isi pointer ke through access hoti hai." Ek promise ya to nibhai jaati hai (regions alag hain → legal, fast) ya todi jaati hai (regions overlap karti hain → undefined behaviour). Neeche har worked example memory mein pointers ki ek concrete arrangement hai, aur hum har baar wahi do sawaal poochte hain: kya regions overlap karti hain? aur kya restrict ka promise isliye rakha gaya ya toda gaya?
Yeh page exhaustive drill hai. Pehle hum ek matrix banate hain jisme C mein aane wale har tarah ke overlap situations hain; phir har cell ke liye ek example solve karte hain.
Theory ke liye parent note dekho: restrict — aliasing hint .
Kisi bhi example se pehle, do terms aur ek abbreviation — kyunki poori page inhe alag rakhne par tikhi hai.
Definition Touch vs. Overlap vs. UB
UB ka matlab hai undefined behaviour : C standard us program ke behavior ke baare mein koi bhi requirement nahi rakhta. Ho sakta hai wo "expected" result de, garbage de, ya optimization levels ke beech alag de. Hum neeche har jagah UB likhte hain.
Do byte-ranges overlap karti hain jab wo kam se kam ek byte share karti hain. Overlap woh cheez hai jo restrict promise ko todti hai.
Do byte-ranges touch karti hain (yani adjacent hoti hain) jab ek exactly wahin khatam hoti hai jahan doosri shuru hoti hai, koi byte share nahi karti. Touching safe hai — yeh overlap nahi hai.
Definition Range likhne ka tarika: inclusive vs. half-open
Is page par do equivalent notations aati hain — dono seekho kyunki C code mein dono milti hain:
Inclusive arr[0..3] matlab indices 0, 1, 2, 3 par chaar elements — dono ends included .
Half-open [0, 4) matlab "0 se shuru karo, 4 se pehle ruko" — square bracket [ left end ko include karta hai, round bracket ) right end ko exclude karta hai. Toh [0, 4) bhi indices 0, 1, 2, 3 hi hain.
Dono same chaar cells describe karte hain. Half-open overlap tests ke liye convenient hai kyunki s se shuru hone wali aur n items cover karne wali range exactly [s, s+n) hai, aur do half-open ranges [a, b) aur [c, d) disjoint hain exactly jab b ≤ c (ya d ≤ a).
Neeche kuch examples mein kaha gaya hai ki result "ek scalar build aur ek vectorized build ke beech alag hota hai." In do words ko use karne se pehle inhe samjhte hain.
Definition Scalar build vs. vectorized (SIMD) build
Scalar build plain, literal machine code hai: loop ko ek element at a time , source order mein process karta hai (jaise dst[0], phir dst[1], ...). Yeh roughly woh hai jo aapko -O0 (no optimization) par milta hai.
Vectorized build woh hai jo ek optimizing compiler (-O2/-O3 par) produce karta hai jab use ek saath kai elements process karne ki permission hoti hai. Modern CPUs ke paas wide registers hote hain jo ek saath 4 ya 8 int rakh sakte hain; ek machine instruction poori batch ko add/copy kar deta hai. Ise SIMD (Single Instruction, Multiple Data) kehte hain — dekho Compiler optimization & vectorization (SIMD) .
Yahan kyun matter karta hai. Ek block batch karne ke liye, compiler aksar pehle saare source elements load karta hai , phir poori batch store karta hai — yeh order tabhi valid hai jab source aur destination overlap na karein. restrict exactly woh promise hai jo is reordering ko unlock karta hai. Toh:
Jab promise rakha jaata hai , scalar aur vectorized builds agree karte hain → ek deterministic answer.
Jab promise toda jaata hai (overlap), dono builds stale-vs-fresh data read kar sakte hain aur alag results produce kar sakte hain — yahi reason hai ki overlap UB hai.
Jab bhi koi example dikhata hai "scalar X deta hai, vectorized Y deta hai, aur X ≠ Y", woh disagreement hi undefined behaviour ka proof hai.
Ab picture. Do arrays memory ki ek flat strip mein rehti hain. Har pointer ek start address aur (kitne elements touch karte ho uske through) bytes ki ek range name karta hai.
Figure s01 (described). Aath numbered memory cells (indices 0–7) ki ek horizontal strip. Neeche ek black arrow cells 2–5 span karta hai jis par "pointer p range" likha hai; upar ek black arrow cells 0–3 span karta hai jis par "pointer q range" likha hai. Do cells jo dono cover karte hain (2 aur 3) red mein outlined hain aur "shared bytes (danger zone)" label kiya gaya hai. Red outline hi overlap hai.
Agar red zone empty hai, promise rakha ja sakta hai. Agar usme ek bhi byte hai, promise toot jaata hai jis moment dono pointers usse touch karte hain.
Yeh har case-class hai jo yeh topic aap par throw kar sakti hai:
Cell
Situation
Regions overlap karti hain?
restrict legal hai?
A
Do alag arrays, different objects
Nahi
✅ Haan
B
Forward-shifted copy dst = src+k, k>0
Haan (partial)
❌ Nahi
C
Backward-shifted copy dst = src-k, k>0
Haan (partial)
❌ Nahi
D
Exact same pointer dst == src, n>0
Fully (ek self-alias)
❌ Nahi
E
Adjacent, touching but not overlapping (dst = src+n)
Nahi (edge case!)
✅ Haan
F
Scalar pointer possibly inside an array (factor in v)
Depends karta hai
✅ Sirf agar bahar ho
G
Zero-length / degenerate (n = 0)
Vacuously none
✅ Haan (koi access nahi)
H
Real-world word problem (image row blur)
Check karna padega
depends
I
Exam twist: const + restrict alias, overlap sirf -O3 par visible
Haan (full)
❌ Nahi
Ab hum har cell, table order A → I mein walk karenge — har ek worked example.
Examples 1–4 do tiny functions call karte hain. Yahan unke signatures hain taaki kuch undefined use na ho:
Worked example Example 1 — Cell A: do alag arrays (promise rakha gaya)
int b [ 4 ] = { 10 , 20 , 30 , 40 };
int c [ 4 ] = { 1 , 2 , 3 , 4 };
int a [ 4 ];
add (a, b, c, 4 );
Forecast: Kya a, b, c overlap karte hain? a baad mein kya hoga?
Ranges list karo. a, b, c teen independent array objects hain. Har ek apne bytes occupy karta hai; C guarantee karta hai ki distinct objects overlap nahi karte.
Yeh step kyun? Poora promise byte ranges ke baare mein hai — toh hum hamesha unhe locate karke shuru karte hain.
Red zone check karo. Har pair ke liye empty. Promise rakha gaya .
Yeh step kyun? Empty danger zone = restrict sach hai = compiler freely vectorize kar sakta hai.
Compute karo. a[i] = b[i] + c[i] → a = {11, 22, 33, 44}.
Yeh step kyun? Kyunki koi aliasing nahi, plain elementwise addition; answer deterministic hai.
Verify: 11+22+33+44 = 110; aur b[0]+c[0] = 10+1 = 11. Deterministic, correct. ✅
Worked example Example 2 — Cell B: forward-shifted copy (promise toda gaya)
int arr [ 5 ] = { 1 , 2 , 3 , 4 , 5 };
copy (arr + 1 , arr, 4 ); // dst = arr+1, src = arr, n = 4
Forecast: Ranges kahan overlap karti hain, aur result garbage kyun hai?
Figure s02 (described). arr ke paanch cells (values 1–5). Ek black arrow indices 0–3 span karta hai "src = arr[0..3]" label ke saath; ek red arrow indices 1–4 span karta hai "dst = arr[1..4]" label ke saath. Teen shared cells 1–3 red mein outlined hain aur "overlap arr[1..3] → UB" label kiya gaya hai.
Ranges locate karo (half-open). src = [0,4), dst = [1,5). Unka intersection [1,4) = indices 1,2,3 non-empty hai. Red zone non-empty ⇒ wo overlap karti hain.
Yeh step kyun? Overlap ⇒ copy par restrict promise ek jhooth hai ⇒ UB.
"Honest" left-to-right (scalar) copy trace karo danger dekhne ke liye. dst[0]=src[0]→arr[1]=1; ab arr={1,1,3,4,5}. dst[1]=src[1] reads arr[1] jo ab 1 hai → arr[2]=1 → {1,1,1,4,5}; dst[2]=src[2] reads arr[2]=1 → arr[3]=1 → {1,1,1,1,5}; dst[3]=src[3] reads arr[3]=1 → arr[4]=1 → {1,1,1,1,1}.
Yeh step kyun? Ek element at a time, left-to-right, har write ek not-yet-read source cell ko corrupt karta hai.
Real answer state karo: UB. Ek vectorized (SIMD) build pehle poora source block {1,2,3,4} registers mein load karta hai, phir dst mein store karta hai, result deta hai {1,1,2,3,4} — phir se alag. Koi single correct output nahi hai .
Yeh step kyun? UB ka matlab hai hum ise predict bhi nahi kar sakte — lesson yeh hai "aisa mat karo." memmove use karo.
Verify: scalar trace {1,1,1,1,1} deta hai; vectorized/SIMD trace {1,1,2,3,4} deta hai. Ek call se do alag results ⇒ genuinely undefined. ✅
Worked example Example 3 — Cell C: backward-shifted copy (phir bhi overlap)
int arr [ 5 ] = { 1 , 2 , 3 , 4 , 5 };
copy (arr, arr + 1 , 4 ); // dst = arr, src = arr+1, n = 4
Forecast: Overlap direction reverse hai — kya left-to-right copy is baar survive karti hai?
Ranges locate karo (half-open). dst = [0,4), src = [1,5). Intersection [1,4) = indices 1,2,3 — phir non-empty. Promise toda gaya .
Yeh step kyun? Overlap symmetric hai; direction shared bytes ko remove nahi karta.
Left-to-right (scalar) trace karo. arr[0]=arr[1]=2→{2,2,3,4,5}; arr[1]=arr[2]=3→{2,3,3,4,5}; arr[2]=arr[3]=4; arr[3]=arr[4]=5 → {2,3,4,5,5}.
Yeh step kyun? Yahan left-to-right ittefaqan "shifted" result deta hai — lekin yeh phir bhi UB hai kyunki aapne no overlap promise kiya tha.
Punchline. Scalar answer sahi lagta hai , phir bhi ek vectorized (SIMD) build stale block read kar sakta hai aur differ kar sakta hai. Legality luck par depend nahi karti.
Yeh step kyun? Cells B aur C milke prove karte hain: overlap illegal hai chahe kaunsi side badi ho.
Verify: scalar result {2,3,4,5,5}; sum 2+3+4+5+5 = 19. Yeh ki answer theek lagta hai yahan exactly woh trap hai. Phir bhi UB. ✅
Worked example Example 4 — Cell D: exact self-alias
dst == src, n>0 (promise toda gaya)
int arr [ 3 ] = { 7 , 8 , 9 };
copy (arr, arr, 3 ); // dst == src, n = 3 -> full overlap
Forecast: Aap ek array ko khud apne upar copy kar rahe ho. Values nahi badlengi — toh kya yeh safe hai?
Ranges locate karo (half-open). dst = [0,3) aur src = [0,3) same range hain. Unka intersection poora hissa hai — maximal overlap .
Yeh step kyun? dst aur src do alag restrict parameters hain jo same bytes name karte hain. Har ek un bytes ka ek alag access path hai ⇒ "sirf is pointer ke through access" ka promise dono ke liye violate hota hai.
Kyun "values unchanged" ise rescue nahi karta. Bhaale hi dst[i] = src[i] har cell ki apni value wapis likhta ho, promise pointer disjointness ke baare mein hai, is baat ke baare mein nahi ki bytes equal end hoti hain ya nahi. Jis moment compiler assume karta hai ki dst aur src kabhi alias nahi karte, use loads/stores ko reorder ya fuse karne ka haq milta hai jo sirf disjointness ke under valid hai. Assumption false ⇒ UB.
Yeh step kyun? "Data ittefaqan equal hai" aur "contract rakha gaya hai" ko alag karta hai — ye do unrelated cheezein hain.
Cell G se contrast karo. n ko 0 karo aur loop kabhi nahi chalta → zero accesses → kuch violate karne ko nahi (Example 7). Toh Cell D ka danger poora n>0 mein rehta hai.
Yeh step kyun? Exactly pin karta hai kaunsa input self-alias ko illegal banata hai.
Verify: values numerically unchanged hain — arr abhi bhi {7,8,9} hai — phir bhi call UB hai kyunki do restrict parameters same region alias karte hain. "Same numbers out" ≠ "legal". ✅
Worked example Example 5 — Cell E: adjacent but NOT overlapping (edge case, promise rakha gaya)
int arr [ 8 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 };
copy (arr + 4 , arr, 4 ); // dst = arr[4..7], src = arr[0..3]
Forecast: Wo index 3 aur 4 ke beech boundary par touch karte hain — kya yeh overlap hai?
Figure s03 (described). arr ke aath cells. Ek black arrow indices 0–3 span karta hai "src = arr[0..3]" label ke saath; doosra black arrow indices 4–7 span karta hai "dst = arr[4..7]" label ke saath. Ek red vertical line cell 3 aur cell 4 ke beech seam mark karta hai, "seam: src_end = dst_start, NO shared byte → legal" label ke saath.
Seam par range test (half-open). src = [0,4), dst = [4,8). Disjointness test: src 4 par khatam hota hai, dst 4 par shuru hota hai, aur 4 ≤ 4 holds ⇒ disjoint . Wo touch karte hain lekin overlap nahi karte.
Yeh step kyun? Yahi "touch ≠ overlap" definition action mein hai.
Conclude promise rakha gaya. dst = src + n exactly boundary case hai; yeh legal hai. Disjointness test src_end ≤ dst_start hai — ek non-strict (≤) inequality, isliye seam par equality phir bhi disjoint count hoti hai.
Yeh step kyun? Classic exam distractor: kyunki ≤ (na ki <) allowed hai, touching case safe hai.
Compute karo. arr[4..7] {1,2,3,4} ban jaata hai → poora array {1,2,3,4,1,2,3,4}.
Yeh step kyun? Disjoint ⇒ deterministic copy.
Verify: result {1,2,3,4,1,2,3,4}; sum = 2*(1+2+3+4) = 20. ✅
Worked example Example 6 — Cell F: scalar pointer array ke andar vs. bahar
void scale ( float * restrict v , const float * restrict factor , int n );
float x [ 3 ] = { 2.0 f , 4.0 f , 6.0 f };
float k = 10.0 f ;
scale (x, & k , 3 ); // factor points OUTSIDE x -> legal
scale (x, & x [ 0 ], 3 ); // factor aliases x[0] -> UB!
Forecast: Dono calls compile hoti hain. Kaun sa promise rakhta hai, aur har ek kya compute karna chahta hai?
Case F-legal: factor = &k. k ek alag object hai, x ke andar nahi. Promise rakha gaya. Compiler *factor = 10 ek baar register mein load karta hai. x = {20, 40, 60}.
Yeh step kyun? Yahi exactly woh register-caching win hai jo parent ne describe ki.
Case F-illegal: factor = &x[0]. Ab factor v ke andar point karta hai. v[0] likhna *factor change karta hai. Overlap ⇒ UB.
Yeh step kyun? Promise "factor, v ke andar nahi point karta" ab false hai.
Dikhao kyun illegal wala ambiguous hai. Honest per-iteration reload (scalar): x[0]=2*2=4; phir *factor 4 hai, toh x[1]=4*4=16, x[2]=6*4=24 → {4,16,24}. Lekin restrict ke saath compiler ne *factor=2 cache kiya, result deta hai x = {4, 8, 12}. Do answers ⇒ UB.
Yeh step kyun? Concretely "load once vs. reload" divergence demonstrate karta hai.
Verify (legal call): {2,4,6} * 10 = {20,40,60}, sum 120. Verify (illegal): cached path {4,8,12} vs. reload path {4,16,24} alag hain ⇒ UB confirmed. ✅
Worked example Example 7 — Cell G: degenerate zero-length (vacuously legal)
int arr [ 3 ] = { 7 , 8 , 9 };
copy (arr, arr, 0 ); // dst == src, but n = 0
Forecast: dst aur src same address hain — surely yeh overlap karta hai?
Accesses count karo. Loop for(i=0;i<0;i++) zero baar chalta hai. Koi load, koi store, koi byte kabhi touch nahi hoti.
Yeh step kyun? Promise us bytes ke baare mein hai jo pointer ke through access hoti hain. Zero accesses ⇒ violate karne ko kuch nahi.
Conclude vacuously legal. n = 0 ke saath dono ranges [0,0) hain — empty; empty ranges overlap nahi kar sakti.
Yeh step kyun? Degenerate inputs cover karne zaroori hain — empty-loop case hamesha safe hai, identical pointers ke saath bhi.
Cell D ke saath n>0 par contrast karo. copy(arr, arr, 3) (Example 4) access shared bytes karta hai ⇒ UB. Sirf n=0 self-alias ko neutralize karta hai.
Yeh step kyun? Safe empty case ko unsafe self-copy case se alag karta hai.
Verify: copy(arr,arr,0) ke baad array unchanged {7,8,9}, sum 24. ✅
Worked example Example 8 — Cell H: real-world word problem (image row averaging)
Statement. Aap ek image row ko in place blur karte ho har pixel ko khud aur apne right neighbour ke average se replace karke: out[i] = (in[i] + in[i+1]) / 2. Ek junior ise restrict ke saath code karta hai aur out == in. Ek row: {100, 200, 40, 80} (last pixel unchanged).
Forecast: out aur in same buffer hone par, kya restrict version valid hai, aur kaun si row appear hoti hai?
Overlap identify karo. out == in ⇒ full overlap (Cell D jaisa) ⇒ restrict promise toda gaya ⇒ UB.
Yeh step kyun? Same buffer maximal overlap hai; yeh image code mein number-one real bug hai.
Honest in-place left-to-right (scalar) computation trace karo (jo ek non -restrict build kar sakta hai): out[0]=(100+200)/2=150; ab in={150,200,40,80}; out[1]=(200+40)/2=120; out[2]=(40+80)/2=60; in[3] untouched =80 → {150,120,60,80}.
Yeh step kyun? Intended-but-fragile result dikhata hai.
Fix state karo. Ya to (a) restrict drop karo aur sequential semantics accept karo, ya (b) alag output buffer mein likho (Cell A) taaki restrict honest rahe aur vectorization legal ho.
Yeh step kyun? Correctness pehle: sirf tab disjointness promise karo jab woh actually sach ho.
Verify: alag buffers ke saath deterministic averages hain (100+200)/2=150, (200+40)/2=120, (40+80)/2=60, last =80 → {150,120,60,80}, sum 410. ✅
Worked example Example 9 — Cell I: exam twist (
const + restrict, aur -O3 surprise)
Statement. Yeh explain karo:
int sum_twice ( const int * restrict p , int * restrict q , int n ) {
* q = 0 ;
for ( int i = 0 ; i < n; i ++ ) * q += p [i]; // accumulate into *q
return * q;
}
int data [ 3 ] = { 5 , 5 , 5 };
int total = sum_twice (data, & data [ 0 ], 3 ); // q aliases p[0]!
Forecast: p const hai (p ke through read-only), q writable hai. Kya const aliasing bug se bachata hai?
Do qualifiers alag karo. const promise karta hai aap p ke through modify nahi karoge . restrict promise karta hai koi doosra pointer p ko overlap nahi karta . Ye independent hain (dekho const qualifier ).
Yeh step kyun? Exam is par rely karta hai ki students inhe confuse karein.
Overlap spot karo. q = &data[0] us region ke andar hai jise p read karta hai — us byte par full overlap. *q likhna p[0] mutate karta hai. Bhaale hi p const ho, memory change hoti hai — q ke through, jo q ke liye legal hai lekin p ka restrict violate karta hai (p ki bytes ko p ke ilawa kisi aur ne touch kiya). ⇒ overlaps ⇒ not legal ⇒ UB.
Yeh step kyun? const p ke through verb ko restrict karta hai, underlying bytes ko nahi.
Split predict karo. Har iteration honest reload (scalar / -O0): *q 0 se shuru; +=p[0] lekin p[0] *q=0 ne abhi 0 set kiya ⇒ 0; +=p[1]=5⇒5; +=p[2]=5⇒10; 10 return karta hai. Lekin -O3 (vectorized) par, compiler restrict par trust karta hai aur loop se pehle original p[0..2]={5,5,5} registers mein cache kar sakta hai ⇒ 5+5+5=15. Do answers ⇒ UB.
Yeh step kyun? Yeh woh infamous "works at -O0, breaks at -O3" bug hai jiske baare mein parent ne warn kiya tha — upar define kiye gaye scalar-vs-vectorized split ka direct consequence.
Verify: scalar/-O0 path 0+5+5 = 10 return karta hai; cached/-O3 path 5+5+5 = 15 return karta hai. 10 ≠ 15 ⇒ undefined behaviour proven. Fix: alias mat karo, ya restrict drop karo. ✅
Recall
Har cell, ek line mein.
Cell A (do arrays) legal? ::: Haan — disjoint, deterministic, vectorizable.
Cell B (dst=src+1) legal? ::: Nahi — partial overlap [1,4), UB.
Cell C (dst=src-1) legal? ::: Nahi — phir bhi overlaps karta hai; ek "sahi lagta" scalar result luck hai.
Cell D (dst==src, n>0) legal? ::: Nahi — full self-alias; do restrict params same bytes name karte hain ⇒ UB, bhaale values unchanged aa jaayein.
Cell E (dst=src+n) legal? ::: Haan — adjacent, koi shared byte nahi (src_end ≤ dst_start, non-strict).
Cell F (scalar array ke andar) legal? ::: Sirf tab agar scalar array ke bahar ho.
Cell G (n=0) legal? ::: Haan — zero accesses promise violate nahi kar sakti, chahe dstsrc ho.
Cell H (in-place blur, out in) legal? ::: Nahi — full overlap; alag output buffer use karo.
Cell I (const+restrict alias) legal? ::: Nahi — const aliasing nahi rokta; -O0 aur -O3 disagree karte hain ⇒ UB.
"Touch ≠ Overlap." Ranges jo boundary par milti hain (Cell E) theek hain; ranges jo ek byte share karti hain (B, C, D, H, I) UB hain. Strip draw karo, red zone mark karo, phir decide karo.
Pointers in C
Pointer aliasing
const qualifier
memcpy vs memmove
Compiler optimization & vectorization (SIMD)
Undefined behaviour in C
C99 standard features