Exercises — volatile keyword — preventing optimization of hardware registers
5.1.33 · D4· Coding › C Programming › volatile keyword — preventing optimization of hardware regis
Level 1 — Recognition
Yahan sirf spot karna hai ki volatile zaroori hai ya nahi aur rule ka naam batana hai. Abhi koi code likhna nahi.
L1.1 — Register hai ya ordinary?
Har variable ke liye batao ki use volatile chahiye (yes / no) aur ek word mein kyon.
- Ek
uint32_tjo ek array ka sum rakhta hai, ek baar use hota hai phir discard ho jaata hai. *(uint32_t *)0x40021000— ek memory-mapped I/O peripheral status register.- Ek global
int donejo ek timer ISR (Interrupt Service Routine) ke andar1par set hota hai,main()dwara poll kiya jaata hai. for(i=0;i<10;i++)mein ek loop counteri.
Recall Solution
| # | volatile chahiye? |
Kyon |
|---|---|---|
| 1 | Nahi | Visible code ke bahar kuch ise change nahi karta. |
| 2 | Haan | Memory-mapped IO — hardware ise change karta hai. |
| 3 | Haan | Compiler ki nazar ke peeche ek ISR dwara change hota hai. |
| 4 | Nahi | Poori tarah visible code se control hota hai; ise cache karna sahi aur desirable hai. |
Rule: volatile sirf tab chahiye jab koi value bina kisi visible-code action ke change ho sake. Teen sources yeh karte hain:
- Hardware register — ek peripheral apne aap bits flip karta hai.
- ISR — ek interrupt handler tumhare apne CPU par tumhari do instructions ke beech chalata hai aur variable likhta hai.
- Doosra thread / DMA — DMA (Direct Memory Access) ek alag hardware engine hai jo RAM mein parallel copy karta hai; ek doosra thread doosre core par concurrently chalta hai. Dono memory change karte hain jab tumhara loop chalta hai, tumhare code mein koi visible write nahi hoti.
Neeche ki figure in teeno "invisible writers" ko dikhati hai jo compiler ke aaround jaake same memory cell tak pahunchte hain.

L1.2 — Kaunsa optimization bite karta?
while (status == 0) { } jahan status ek hardware flag hai bina volatile ke. Teen forbidden optimizations mein se kaunsa bug produce karta hai: value ko register mein cache karna (loop-invariant code motion), dead-store elimination, ya reordering?
Recall Solution
Value ko register mein cache karna. Kyunki kuch bhi visible loop ke andar status nahi likhta, compiler load ko loop-invariant maanta hai — iske value iterations mein change nahi ho sakti — aur loop-invariant code motion (LICM) apply karta hai: load ko loop se bahar ek CPU register mein hoist kar deta hai, phir us register copy par forever loop karta hai.
- "Loop-invariant code motion" standard optimizer ka naam hai; "caching in a register" yahan iske effect ka roz ka tarika describe karna hai.
- (Dead-store elimination un writes ke baare mein hai jo kabhi read nahi hote; reordering accesses move karne ke baare mein hai — dono yahan cause nahi hain.)
Level 2 — Application
Ab chhote snippets likho ya fix karo.
L2.1 — Hang fix karo
Yeh ek UART "data-ready" bit (address 0x4000C000 par status register ke bit 0) ko poll karta hai. -O2 par hang ho jaata hai. Ise minimal change ke saath fix karo.
uint8_t *SR = (uint8_t *)0x4000C000;
while (!(*SR & 0x01)) { }Recall Solution
Pointed-to data ko volatile banao:
volatile uint8_t *SR = (volatile uint8_t *)0x4000C000;
while (!(*SR & 0x01)) { }Kyun target aur pointer nahi? Hum *SR har loop mein re-read karte hain, isliye jo cheez read ho rahi hai woh volatile honi chahiye. Right-to-left parse karte hue: volatile uint8_t *SR = "SR ek pointer hai volatile uint8_t ki taraf". Dekho pointer qualifiers.
Kyun & 0x01? Status register bahut saare flags pack karta hai; 0x01 bit 0 ko chhod kar sabhi bits mask karta hai taaki hum sirf "data ready" test karein.
L2.2 — Guaranteed memory reads count karo
volatile uint32_t *FIFO = (volatile uint32_t *)0x40000010;
uint32_t a = *FIFO;
uint32_t b = *FIFO;
uint32_t c = *FIFO;Har read ek hardware FIFO (First-In-First-Out queue — upar jargon box dekho) se ek word pop karta hai. -O2 par compiler kitne real memory reads emit karta hai? Bina volatile ke count kya hota?
Recall Solution
volatileke saath: 3 reads. Ek volatile access ek observable side effect hai, isliye compiler inhe merge nahi kar sakta.volatileke bina: 1 read. Compiler same address ke teen reads dekhta hai beech mein koi write nahi aur inheb = a; c = a;mein collapse kar deta hai — do FIFO words kho jaate hain.
Jawab: 3 saath mein, 1 bina.
L2.3 — Qualifier kahan lagaao
Ek read-only hardware status register: tumhara code kabhi ise nahi likhega, lekin hardware ise change karta hai. Address 0x40000004, size uint32_t ke liye pointer declaration likho.
Recall Solution
const volatile uint32_t *STATUS = (const volatile uint32_t *)0x40000004;Dono kyun? const (from const qualifier) compiler ko tumhare code mein koi bhi *STATUS = ... reject karne par majboor karta hai — ek compile-time guard. volatile har baar fresh read force karta hai kyunki hardware ise mutate karta hai. Ye opposites nahi hain: const = "main nahi likhta", volatile = "koi aur likhta hai".
Figure teen placements graphically parse karta hai (right-to-left padho).

Level 3 — Analysis
Ab kyun generated code differ karta hai iske baare mein reason karo.
L3.1 — Assembly predict karo
Neeche do loops hain. Har ek ke liye batao ki load -O2 output mein loop body ke andar hai ya bahar, aur kyun.
// (A)
int flag = 0;
while (flag == 0) { }
// (B)
volatile int flag = 0;
while (flag == 0) { }Recall Solution
(A) non-volatile — load BAHAR hai. Line by line chalo:
load R, [flag] ; EK read, loop ke UPAR hoist kiya
L: cmp R, 0 ; cached register R compare karo
jeq L ; R kabhi nahi badlega -> forever wapas jump karo
Compiler ne prove kiya ki loop mein kuch flag nahi likhta, isliye load loop-invariant hai aur hoist ho gaya. Loop ke andar ab koi memory access nahi — sirf register R test hota hai. Agar koi bahari agent (ISR/hardware) memory mein likhta hai, R kabhi nahi jaanata ⇒ hang.
(B) volatile — load ANDAR hai.
L: load R, [flag] ; har iteration mein memory RE-READ karo
cmp R, 0
jeq L
Kyunki volatile read ek observable side effect hai, compiler ise hoist karne se mana hai. load label L ke neeche rehta hai, isliye loop ka har chakkar real memory touch karta hai aur koi bhi bahari write dekhta hai.
Ek-line difference: (A) mein load L ke upar hai (ek baar execute hota hai); (B) mein yeh L ke neeche hai (har iteration mein execute hota hai).
Neeche ki figure ek timeline hai: wahi ISR write time t par aati hai, lekin sirf volatile loop mein t ke baad ek memory read hai jo ise notice kare.

L3.2 — Kya -O0 (A) ko "fix" karta hai?
-O0 par loop (A) lagta hai kaam karta hai. Kya -O0 ek valid fix hai? Correctness vs. accident ke terms mein explain karo.
Recall Solution
Nahi — yeh accidental hai, correct nahi.
-O0ittefaq se re-read rakhta hai sirf isliye kyunki yeh loop-invariant code motion nahi karta (dekho Compiler Optimization Levels (-O0 -O2)).- C standard abhi bhi kisi bhi level par non-volatile variable ko cache karne ki permission deta hai; doosra compiler ya future
-O2build ise fir tod dega. - Yeh saari jagah saari optimizations bhi disable karta hai, performance tanking karta hai.
Correct fix = volatile, jo har optimization level par sahi hai aur intent document karta hai.
L3.3 — Doosra read kyun vanish ho jaata hai
L2.2 mein bina volatile ke, exactly kaunsi optimization ne reads 2 aur 3 remove kiye? Iska naam batao aur woh false assumption bolo jis par yeh depend karta tha.
Recall Solution
Common-subexpression elimination / redundant-load elimination. False assumption: "kisi memory location ki value tab tak nahi badlti jab tak visible code ise nahi likhta, isliye same address ko dobara padhne par same result milta hai." RAM ke liye sach, galat ek FIFO ke liye jahan read ka khud ek side effect hai (queue advance karna).
Level 4 — Synthesis
Ideas ko ek chhote sahi design mein combine karo.
L4.1 — Shared flag sahi tarike se
main() ek byte ka intezaar karta hai jo ek ISR (Interrupt Service Routine) ne ek 4-byte struct mein assemble kiya, ready se signal kiya:
struct { uint8_t b[4]; } packet; // ISR se fill hota hai
int ready = 0; // ISR fill ke baad 1 set karta hai
// main:
while (ready == 0) { }
process(packet);Do bugs yahan chhupe hain. Dono naam batao aur corrected declarations + missing safety step do.
Recall Solution
Bug 1 — ready volatile nahi: poll loop ise cache karta hai → hang. Fix: volatile int ready = 0;.
Bug 2 — koi ordering guarantee nahi: volatile ke saath bhi, compiler/CPU main ko ready == 1 packet bytes visible hone se pehle dikha sakta hai, kyunki volatile packet aur ready ke across koi barrier nahi deta (dekho Memory Barriers and Ordering). Ek memory barrier ek fence instruction hai jo force karta hai ki iske pehle ki saari writes iske baad ki kisi bhi write se pehle visible ho jayein.
Fix — payload writes aur flag ke beech ek barrier daalo, taaki packet pehle guaranteed visible ho:
volatile int ready = 0;
// ISR ke andar:
void isr(void) {
packet.b[0] = a; packet.b[1] = b;
packet.b[2] = c; packet.b[3] = d; // (1) payload likho
__sync_synchronize(); // (2) full memory barrier (GCC/Clang builtin)
ready = 1; // (3) ab flag publish karo
}__sync_synchronize() fence guarantee karta hai ki steps (1) memory mein land ho jayein step (3) se pehle.
memory_order_release kya hai? C11 model tumhe ek atomic write par ek ordering tag attach karne deta hai. Ek release write ek one-directional fence hai: yeh guarantee karta hai ki iske pehle likhi gayi sab cheez kisi bhi reader ko us released value ko dekhne se pehle visible ho jaaye. Isliye ready ka ek release-tagged write "pehle payload publish karo" barrier ko flag write mein hi bundle kar deta hai — reader ise ek acquire read se pair karta hai. Alag __sync_synchronize() se zyada clean:
_Atomic int ready = 0; // dekho [[Atomic operations and _Atomic]]
atomic_store_explicit(&ready, 1, memory_order_release); // payload publish karo, phir flagvolatile akela flag ki visibility fix karta hai, payload ki ordering nahi.
L4.2 — Jab volatile kaafi nahi
main karta hai shared_count++ jahan shared_count ek ISR se bhi increment hota hai. Tumne ise volatile banaya. Kya program ab correct hai? Agar nahi, toh fix kya hai?
Recall Solution
Correct nahi. volatile har access ko memory par force karta hai, lekin shared_count++ load → modify → store hai — teen alag accesses. Load aur store ke beech ek interrupt fire hone par ek increment lost ho jaata hai (ek lost-update race).
Fix: `_Atomic` use karo: _Atomic int shared_count; aur atomic_fetch_add(&shared_count, 1);, ya update ke around interrupts disable karo.
Slogan: volatile ⇒ fresh, atomic nahi.
Level 5 — Mastery
Poora reasoning, edge cases, aur ek numeric trace.
L5.1 — FIFO loss trace karo
Ek driver ek 4-word FIFO (First-In-First-Out queue) ko ek non-volatile loop mein padhta hai jise compiler collapse kar deta hai. FIFO physically words [10, 20, 30, 40] hold karta hai (word 0 pehle pop hota hai). (Buggy) code yeh hai:
uint32_t *FIFO = (uint32_t *)0x40000010;
uint32_t sum = 0;
for (int i = 0; i < 4; i++)
sum += *FIFO; // NOT volatileCompiler load ko loop se bahar hoist kar deta hai (ek real read, value cached).
(a) Single hoisted read kaunsa value return karta hai (pehla FIFO word)?
(b) Buggy code kya sum compute karta hai?
(c) volatile ke saath sum kya hona chahiye?
(d) Kitne FIFO words lost hain (kabhi pop nahi hue), aur unki combined value kya hai?
Recall Solution
(a) Single read pehla word pop karta hai: 10.
(b) Buggy: cached 10 char baar add hota hai → sum = 10 + 10 + 10 + 10 = 40.
(c) Correct (volatile 4 real reads force karta hai 10, 20, 30, 40 pop karke): sum = 10 + 20 + 30 + 40 = 100.
(d) Sirf word 0 kabhi read hua. Words 20, 30, 40 kabhi pop nahi hue → 3 words lost, combined value 90.
Consistency check: correct sum (100) minus buggy sum (40) 60 hai, jo lost-words total (90) ke same nahi — kyunki bug dono 20,30,40 drop karta hai aur 10 ko teen extra baar double-count karta hai. Woh mismatch khud hi lesson hai: caching total ko do tarike se corrupt karta hai ek saath.
Fix: volatile uint32_t *FIFO = (volatile uint32_t *)0x40000010;.
L5.2 — const volatile full case matrix
Ek const volatile uint32_t *reg ke liye, har action ke liye decide karo ki yeh compile hota hai aur kya yeh ek real memory access karta hai:
uint32_t x = *reg;(read)*reg = 5;(write)
Recall Solution
| Action | Compile hota hai? | Real memory access? |
|---|---|---|
x = *reg; |
Haan | Haan — volatile har baar ek fresh read force karta hai. |
*reg = 5; |
Nahi | — const write ko compile error banata hai. |
Yeh exactly ek read-only hardware status register hai: hardware ise likhta hai (volatile ⇒ re-read), tumhara code sirf ise padh sakta hai (const ⇒ writes reject).
L5.3 — Guaranteed accesses count karo (degenerate cases)
volatile int v; ke liye, standard har line ke liye kitne real memory accesses guarantee karta hai?
v = 0; v = 0;int t = v; t = v;(tordinary)v;(ek expression statement jovpadhta hai aur discard karta hai)
Recall Solution
- 2 writes. Volatile writes observable side effects hain — compiler "dead" doosre store ko drop nahi kar sakta. (Non-volatile, yeh 1 emit karta.)
- 2 reads. Dono
vreads memory hit karte hain (tmerges irrelevant hain — v ke reads count hote hain). - 1 read. Ek discarded volatile read bhi ek observable side effect hai aur hona hi chahiye; ek non-volatile
v;poori tarah delete ho jaata.
Totals: 2, 2, 1.
L5.4 — Narrow-bus atomicity trap
Ek volatile uint32_t tick ek timer ISR dwara likha jaata hai aur main() mein ek 8-bit MCU par padha jaata hai (data bus ek baar mein ek byte move karta hai). Tumne reason kiya: "yeh volatile hai, isliye read ek single fresh memory access hai — safe." Kya tick ka ek single read ek consistent 32-bit value return karna guaranteed hai?
Recall Solution
Nahi. Ek 8-bit MCU par ek 32-bit volatile load char alag byte reads mein compile hota hai (data bus sirf 8 bits wide hai — jargon box dekho). Timer ISR un byte reads ke beech fire ho sakti hai aur tick update kar sakti hai, isliye main ek torn value assemble kar sakta hai: kuch bytes purane, kuch naaye (e.g. 0x00FF padha, ISR 0x0100 bump karta hai, tum padhna finish karte ho aur 0x01FF milta hai — ek value jo kabhi exist hi nahi ki).
volatileguarantee karta hai ki har byte access fresh aur real hai, lekin yeh char-byte sequence ko atomic nahi banata.- Ek 32-bit CPU par wahi
uint32_tek bus transaction mein move hota hai, isliye wahan ek access atomic hai — yeh bug width-dependent hai, isliye 32-bit dev board se 8-bit target par port karte waqt miss karna aasaan hai. - Fix: `_Atomic` use karo, ya
tickko do baar padho aur retry karo jab tak do reads agree na karein, ya read ke around interrupts briefly disable karo. Slogan:volatile⇒ fresh, atomic nahi — aur "ek C statement" same nahi hai "ek bus transaction" ke.