Worked examples — Common memory errors — null dereference, buffer overflow, use-after-free, double free, memory leak
5.1.18 · D3· Coding › C Programming › Common memory errors — null dereference, buffer overflow, us
Kisi bhi code se pehle, ek picture jo us vocabulary ko fix kare jo hum baar baar use karte hain. Neeche ki figure mein, memory ko numbered lockers ki ek row ki tarah draw kiya gaya hai. Yellow sticky note p ko dekho: isme tumhara data nahi hota, yeh sirf ek locker number ("4") hold karta hai aur ek arrow us locker ki taraf point karta hai. Teen blue-outlined lockers (3, 4, 5) tumhara owned block hain. Dayi taraf pink note dekho — woh same number "4" dikhata hai lekin uska arrow dashed hai: yeh ek dangling pointer hai jab block free ho jaata hai. Conclusion: do notes ek hi locker ko naam de sakte hain, lekin sirf ek ka uspe legal ownership hota hai.

Figure s01 neeche definition box ki vocabulary illustrate karta hai — jab bhi "owned block" ya "dangling pointer" aaye, isko refer karo.
Scenario matrix
Neeche har cell ek alag situation hai jisme ek memory bug ho sakta hai. Jo examples aate hain wo un cell(s) ke saath tagged hain jo wo cover karte hain, taaki tum dekh sako ki poora grid fill hua hai.
| # | Cell class | Concrete situation | Example |
|---|---|---|---|
| C1 | Null path — failure | malloc returns NULL, pointer dereferenced |
Ex. 1 |
| C2 | Null path — success + guard | malloc succeeds, guarded correctly |
Ex. 1 |
| C3 | Bounds — string, off-by-one | Buffer \0 byte ki wajah se chhota |
Ex. 2 |
| C4 | Bounds — loop index degenerate | Array par <= vs < |
Ex. 3 |
| C5 | Zero / degenerate size | malloc(0), empty string, n = 0 |
Ex. 4 |
| C6 | Lifetime — use-after-free | Freed pointer se read/write | Ex. 5 |
| C7 | Lifetime — double free | free do baar call hua |
Ex. 6 |
| C8 | Leak — reassignment / early return | Last handle kho gaya | Ex. 7 |
| C9 | Real-world word problem | Long-running server, bytes count karna | Ex. 8 |
| C10 | Exam twist | Safe lagta hai, UB hai — do rules combine | Ex. 9 |
| C11 | Combined bug in the null path | malloc fail hota hai, phir null use hota hai aur double-free bhi |
Ex. 10 |
Recall Padhne se pehle quick self-test
Kaun si ek akeli line of code C6 aur C7 dono ko neutralise karti hai? ::: p = NULL; immediately free(p) ke baad — kyunki free(NULL) guaranteed no-op hai aur baad mein *p ek detectable null deref ban jaata hai.
Example 1 — C1 & C2: null path, dono branches
Step 1 — Puchho malloc kya promise karta hai.
malloc(bytes) us many bytes ke ek fresh block ka address return karta hai, ya NULL agar jagah na mile.
Yeh step kyun? Kyunki poora bug isi par hinga karta hai ki in dono mein se kya hua, aur upar ka code ne kabhi pucha hi nahi.
Step 2 — Request ka size nikalao.
sizeof(int) ek typical machine par bytes hota hai. Toh request hai
Yeh step kyun? Itni badi request aksar fail hoti hai, isliye NULL branch theoretical nahi, realistic hai.
Step 3 — Failure branch trace karo (cell C1).
Agar malloc NULL return kare, toh p == 0. Line p[0] = 42; ka matlab hai "address 0 + 0 par 42 likho". Address 0 OS ke through unmapped hai → segfault (null dereference, UB).
Yeh step kyun? Hume failure branch ko uske consequence tak follow karna padega — warna hum kabhi nahi dekhte ki crash write se aata hai, allocation se nahi.
Step 4 — Fix (cell C2).
int *p = malloc(sizeof(int) * 1000000000ULL);
if (p == NULL) { fprintf(stderr, "out of memory\n"); return 1; }
p[0] = 42; // ab guaranteed valid haiYeh step kyun? Ek if ek random crash ko ek controlled, reported failure mein badal deta hai.
Verify: bytes requested ; GiB mein GiB. Forecast ka jawab: kabhi kabhi (sirf tab crash hota hai jab OS allocation refuse kare), aur risky line
p[0] = 42hai,mallocnahi.
Example 2 — C3: classic string off-by-one
Is pure example mein = string length (copy hone wale text mein visible characters ki tadaad) aur = buffer size (destination array ke bytes ki tadaad).
Step 1 — Source bytes gino.
"hello world" mein visible characters hain, isliye . Ek C string hamesha ek invisible null terminator \0 se khatam hoti hai (dekho Strings in C and the null terminator). Isliye bytes mein true length hai
Yeh step kyun? strcpy jab tak aur jab tak \0 tak copy karta hai; yeh destination ka size kabhi nahi dekhta.
Step 2 — Parent note ki size law apply karo.
bytes ka buffer chars ki string ko legally hold kar sakta hai sirf agar ho. Yahan (char buf[8] se), isliye
Yeh step kyun? Yahi woh single inequality hai jo har string-overflow question decide karti hai — hum eyeballing ki jagah apne numbers isme plug karte hain.
Step 3 — Overflow measure karo.
End ke baad write hue bytes . Woh bytes stack par buf ke baad jo bhi hai use clobber karte hain — shayad ek saved return address (dekho Buffer Overflow Exploits).
Yeh step kyun? Exact overflow count jaanna batata hai kitni adjacent memory corrupt hui, jispar ek exploit ya crash actually depend karta hai.
Step 4 — Fix.
snprintf(buf, sizeof buf, "%s", "hello world"); // zyada se zyada 8 bytes likhta hai, hamesha \0-terminatedYeh step kyun? snprintf ko size batayi jaati hai, isliye woh overflow ki jagah truncate karta hai.
Verify: , , overflow bytes.
buf[8]meinsnprintfse truncated result"hello w"hai (7 chars +\0).
Example 3 — C4: fencepost loop
= array size (array mein slots ki tadaad — yahan from int a[5]).
Step 1 — Legal indices list karo.
Array a[5] ke valid indices hain — exactly slots, top index .
Yeh step kyun? C 0 se index karta hai, isliye aakhri valid index hamesha size se ek kam hota hai.
Step 2 — Loop asliyat mein kya visit karta hai.
i <= 5 run karta hai i = 0,1,2,3,4,5. Aakhri i = 5 write karta hai a[5] — end se ek past.
Yeh step kyun? <= boundary include karta hai; < nahi karta.
Step 3 — Damage count karo.
Loop baar run karta hai lekin sirf slots legal hain, isliye out-of-bounds writes . Array ke baad ek int (4 bytes) stomp hota hai.
Yeh step kyun? "Off-by-one" ko explicit count mein turn karna makes it undeniable ki exactly ek write illegal hai.
Step 4 — Fix. <= ko < se badlo: for (int i = 0; i < 5; i++).
Verify:
<=wala loop baar iterate karta hai ( se tak), legal slots , isliye out-of-bounds writes .<wala loop exactly baar iterate karta hai.
Example 4 — C5: zero aur degenerate sizes
Step 1 — Zero ka rule padho.
malloc(0) ya NULL ya ek unique non-NULL pointer return kar sakta hai jise tum dereference nahi kar sakte. Dono legal hain.
Yeh step kyun? Kaafi overflow bugs is assumption se aate hain ki size positive hai; zero ek real case hai jo handle karna padta hai.
Step 2 — Check karo ki free dono branches mein safe hai.
- Agar
p == NULL:free(NULL)guaranteed no-op hai → safe. - Agar
p != NULL: yeh ek real (zero-usable-byte) block hai →free(p)correct hai.
Toh snippet ek bug nahi hai. Bug hoga p[0] = 'x'; — zero-size block read/write karna UB hai.
Yeh step kyun? Kyunki "kya yeh bug hai?" ka jawab sirf dono possible return values check karke diya ja sakta hai — single-branch check aadha case miss kar deta.
Step 3 — Degenerate string case.
Empty string "" abhi bhi byte occupy karta hai (sirf \0), jahan string length hai. Isliye char e[1]; strcpy(e, ""); legal hai; char e[0] terminator bhi hold nahi kar sakta.
Yeh step kyun? Empty string sabse chhota input hai; agar hamaari size law yahan bhi hold karti hai, toh everywhere hold karti hai, isliye hum boundary explicitly test karte hain.
Verify: empty string byte count ;
""ke liye minimum buffer .free(NULL)koi operation nahi karta (crash nahi).
Example 5 — C6: use-after-free, aur kyun free tumhari madad nahi kar sakta
Upar definition box ki do abbreviations yaad karo: UAF = use-after-free, aur UB = undefined behaviour.
Step 1 — free(p) actually kya change karta hai.
free address ki ek copy receive karta hai (pass-by-value). Woh block allocator ki free-list ko return karta hai lekin tumhara variable p touch nahi karta — figure dekho. Neeche Figure s02 mein, blue arrow follow karo: address 0x40 ki sirf ek copy free() mein jaati hai. Ab left par yellow box dekho — tumhara variable p us arrow se kabhi reach nahi hota, isliye call ke baad woh abhi bhi 0x40 hold karta hai (pink label), ab ek aise locker ko point karta hai jo allocator ne wapas le liya (bottom par pink heap block). Picture is pure step ka poora argument hai: free p clear nahi kar sakta kyunki usne kabhi p dekha hi nahi.

Figure s02 upar Step 1 ka visual proof hai: blue "copy" arrow
free()mein kabhi yellowpbox tak nahi pahuncha, isliyepdangling reh jaata hai.
Yeh step kyun? Yahi poori misconception hai: log sochte hain free p ko "clear" kar deta hai. Yeh nahi ho sakta; usne kabhi p nahi dekha, sirf uski value dekhi.
Step 2 — Read diagnose karo.
free ke baad, p dangling hai: woh abhi bhi old locker ko naam deta hai, lekin woh locker already agli malloc ko de diya gaya hoga. *p padhna UAF (use-after-free) hai — yeh 7 print kar sakta hai, garbage print kar sakta hai, ya crash ho sakta hai. Classic kabhi kabhi wala behaviour.
Step 3 — Fix.
free(p);
p = NULL; // ab *p ek detectable null deref hai, silent corruption nahiVerify: free se pehle likhi gayi value
7hai; uski legal read (free se pehle)7hai.freeke baad,*pUB hai, isliye koi deterministic value guaranteed nahi.
Example 6 — C7: double free
Step 1 — Ownership trace karo.
Pehle free(p) ke baad, 10-byte block allocator ke paas wapas hai. p abhi bhi uska address hold karta hai (dangling, unchanged).
Step 2 — Doosra free.
free(p) same address dobara de deta hai. Allocator ek already-free block ko apni free-list mein link karne ki koshish karta hai → metadata corruption, usually modern libc par abort().
Yeh step kyun? Allocator bookkeeping (size, next-free) block ke saath inline store karta hai; re-freeing uspe scribble karta hai.
Step 3 — Ek-line cure (C6 aur C7 dono ko khatam karta hai).
free(p);
p = NULL; // doosra "free(p)" ab free(NULL) hai → guaranteed no-op
free(p); // harmlessVerify: ek live block ke distinct frees ki tadaad yahan hai; ek allocation ke liye correct count hai; excess → double free.
p = NULLke baath, doosri callNULLpar operate karti hai (no-op).
Example 7 — C8: reassignment se leak
Step 1 — Allocations aur frees gino. Allocations (100 bytes, phir 200). Frees . Yeh step kyun? Conservation law kehta hai ek correct program se khatam hota hai; yahan .
Step 2 — Lost handle identify karo.
Line p = malloc(200) 100-byte block ka single pointer overwrite kar deta hai. Woh block ab unreachable hai → leaked.
Step 3 — Quantify karo. Leaked bytes . Freed bytes . Net difference orphaned block.
Step 4 — Fix. Reassign karne se pehle pehla block free karo, ya do variables use karo.
Verify: , , leaked bytes , freed bytes , orphaned blocks .
Example 8 — C9: real-world word problem (ek leaking server)
Step 1 — Bytes leaked per second. Yeh step kyun? Har un-freed request exactly ek 512-byte block add karti hai ( badhta hai, flat rehta hai), isliye leak ek steady stream hai jis par hum rate laga sakte hain.
Step 2 — Total budget bytes mein. Yeh step kyun? Rate bytes/s mein hai, isliye budget same unit (bytes) mein hona chahiye divide karne se pehle — warna units cancel nahi honge. Note karo hum GiB () use karte hain, GB () nahi.
Step 3 — Exhaustion tak time, seconds mein. Yeh step kyun? Ek total amount (bytes) ko per-second rate (bytes/s) se divide karne par seconds bachte hain — bytes cancel ho jaate hain, time milta hai.
Step 4 — Seconds ko hours mein convert karo. Ek ghante mein seconds hote hain, isliye Yeh step kyun? Isliye hi leaks itne khatarnak hote hain: lagbhag do din kuch crash nahi hota, phir raat 3 baje OOM-killer strike karta hai. Valgrind and AddressSanitizer jaise tools ise seconds mein pakad lete hain.
Step 5 — Fix. Har session malloc ko request end par free se match karo (ek block ka ek owner).
Verify: rate B/s; budget B; s; h.
Example 9 — C10: exam twist (safe lagta hai, UB hai)
Step 1 — Bounds check karo (decoy).
"id-7" mein chars hain, isliye (buffer size). Overflow rule satisfy hai. Yahan koi buffer overflow nahi — yahi trap hai.
Yeh step kyun? Hume pehle obvious suspect clear karna hoga, taaki real bug uske peeche chhipne ki jagah saamne aaye.
Step 2 — Lifetime check karo.
tag stack par rehta hai (dekho Stack vs Heap Memory). Jab make_tag return karta hai, uska stack frame destroy ho jaata hai. tag return karna ek aisi memory ka pointer return karna hai jo ab kisi ki nahi — ek dangling pointer, aur koi bhi use UAF-flavoured UB hai.
Yeh step kyun? Yahan do rules apply hote hain (bounds AUR lifetime); exam real bug ko ek sahi-se-handle kiye gaye bug ke peeche chhupata hai.
Step 3 — Fix (heap-allocate karo taaki caller ka ownership ho).
char *make_tag(void) {
char *tag = malloc(16);
if (!tag) return NULL;
snprintf(tag, 16, "id-%d", 7);
return tag; // caller ko free() karna hoga
}Verify: content length check hold karta hai (toh decoy sach mein safe hai); actual defect stack address return karna hai, bounds error nahi.
Example 10 — C11: null path mein do bugs stack
Step 1 — State establish karo.
Maano malloc fail hua, isliye p == NULL (address 0). Agle har line ko us ek fact ke khilaf judge karna hai.
Yeh step kyun? Combined bugs sirf tab confusing hote hain jab tak pointer ki actual value pin nahi hoti; jab p == NULL fix ho jaata hai, har line decidable ho jaati hai.
Step 2 — Line (a): null dereference.
p[0] = 'x'; address 0 par likhta hai → segfault, ek null dereference (UB). Yeh ek genuine bug hai.
Yeh step kyun? Yeh dikhata hai ki null path ka pehla shikaar dereference hai, exactly Example 1 ki tarah.
Step 3 — Line (b): free(NULL) safe hai.
Kyunki p == NULL, free(p) hai free(NULL), jo standard guarantee karta hai kuch nahi karta. Harmless.
Yeh step kyun? Yeh surprising cell hai: "failure path mein free" bug nahi hai jab pointer null ho.
Step 4 — Line (c): abhi bhi safe, double free nahi.
Block kabhi actually allocate hi nahi hua, isliye dobara free karne ke liye kuch hai hi nahi. free(NULL) phir se → no-op. Toh usual double-free danger null path mein evaporate ho jaata hai.
Yeh step kyun? Yeh prove karta hai ki double-free ke liye ek real, ek baar freed block chahiye — NULL ko do baar free karna double free nahi hai.
Step 5 — Asli lesson.
Yahan sirf line (a) bug hai. Agar malloc ke baad if (!p) return; check kiya hota, teeno lines skip ho jaatein. Example 1 ka ek akela guard combined scenario ko bhi dissolve kar deta hai.
Yeh step kyun? Yeh combined case ko usi ek discipline (malloc check karo) se jodta hai jo ise khatam kar deti hai.
Verify:
p == NULLke saath: bug count (sirf line (a));free(NULL)do baar call hoke operations karta hai; freed real blocks ki tadaad , isliye double-free count .
Coverage check
Recall Kya matrix ke har cell ko fill kiya?
C1/C2 → Ex.1 · C3 → Ex.2 · C4 → Ex.3 · C5 → Ex.4 · C6 → Ex.5 · C7 → Ex.6 · C8 → Ex.7 · C9 → Ex.8 · C10 → Ex.9 · C11 → Ex.10. ::: Gyaarahon cells cover hue.
Flashcards
char buf[8] mein strcpy(buf,"hello world") kitna overflow size cause karta hai?
\0), buffer 8 hold karta hai, isliye yeh end se 4 bytes past likhta hai.int a[5] par for(i=0;i<=5;i++) kitne out-of-bounds writes karta hai?
i==5 wala write jo a[5] par karta hai).Kya malloc(0) ka result free kar sakte ho?
NULL return karta hai (free no-op hai) ya ek valid non-dereferenceable block (free correct hai).p=malloc(100); p=malloc(200); free(p); mein kitne bytes leak hote hain?
Kyun local char tag[16] return karna bug hai jab sahi size ka bhi ho?
tag stack par rehta hai; return hone par frame destroy ho jaata hai, dangling pointer bachta hai → UB.1000000000ULL jaise literal par ULL suffix kya karta hai?
int overflow na kare.Jab p == NULL ho, kya free(p); free(p); double free hai?
free(NULL) guaranteed no-op hai; koi real block free nahi hota, isliye double free nahi.