5.1.14 · D3 · Coding › C Programming › Dynamic memory — malloc, calloc, realloc, free
Yeh page parent topic ke liye drill-ground hai. Hum har tarah ke case se guzarte hain jo ek heap-allocation problem mein aa sakta hai: normal sizes, number zero , degenerate (empty) requests, allocation failure , growing, shrinking , byte count ka integer overflow , ek word problem, aur ek exam trap. Kuch bhi chhupaaya nahi gaya.
Aapko pehle se Pointers in C , sizeof operator , aur The Stack and the Heap se milna chahiye tha. Agar koi symbol unfamiliar lagta hai, toh woh cue hai unhe dobara dekhne ka.
Definition Do words jo overflow examples mein milenge
size_t — ek unsigned integer type (from <stddef.h>) jo exactly itna wide hai ki kisi bhi object ka size bytes mein hold kar sake. malloc, calloc, aur realloc teeno apna size argument size_t ke roop mein lete hain, aur `sizeof` ek size_t produce karta hai. Sizes ke liye size_t use karne se aapka arithmetic usi width mein hota hai jo malloc expect karta hai.
SIZE_MAX — ek constant (from <stdint.h>) jo ==size_t ki largest value ke barabar hai==. 32-bit size_t par yeh 2 32 − 1 = 4294967295 hai. Agar koi product SIZE_MAX se zyada ho jaaye toh woh wrap around ho jaata hai (0 ke paas vapas aa jaata hai) bade hone ki jagah — woh silent wrap hi overflow bug hai jissse hum bachte hain.
Har heap call ko ek grid par ek point samjho. Do axes jo matter karte hain woh hain "main kitna maang raha hoon?" (aapka request kiya hua size) aur "allocator ka jawaab kya hai?" (ek real block, ya NULL). Yeh poora grid hai — har cell ka ek worked example neeche diya gaya hai.
Cell
Situation
Isme kya tricky hai
Covered by
C1
Normal malloc of n objects, success
byte count nikalna, garbage init
Example 1
C2
calloc for zeroed data
zero kyun matter karta hai, two-arg form, iska apna multiply overflow
Example 2
C3
malloc(0) — zero-size request
NULL ya freeable non-NULL return kar sakta hai
Example 3
C4
Allocation failure (NULL returned) + byte-count overflow
check karna zaroori; product wrap ho sakta hai
Example 4
C5
realloc se grow , block moves
baad mein old pointer invalid, copy hoti hai
Example 5
C6
realloc se shrink
new size ke baad ka data lost ho jaata hai
Example 6
C7
realloc(NULL, n), realloc(p, 0), realloc(NULL, 0) — degenerate edges
malloc/free/malloc(0) ki tarah behave karte hain
Example 7
C8
Word problem : unknown-length input padhna
grow-loop + free combine karo
Example 8
C9
Exam twist : bug dhundho
self-assign leak + overflow
Example 9
Geometry ka sign/quadrant idea yahan sizes par map hota hai: positive-normal (C1, C2), zero (C3, C7), aur "answer is nothing" case (C4). Growing vs shrinking (C5, C6) change ki do directions hain. Ek hidden fourth axis yeh hai ki product n * sizeof(*p) khud malloc ke dekhne se pehle overflow ho jaata hai (C4, C9): ek "small-looking" request bhi wrap ho ke ek tiny number ban sakti hai aur aapko ek dangerously undersized block de sakti hai.
Neeche di gayi picture us poore grid ka map hai, toh ise dhyan se padho:
Horizontal line size axis hai — jaise aap right move karte ho waise aap kitne bytes maang rahe ho yeh badhta jaata hai. Left end size 0 hai , middle ek normal request hai , right end "too big to satisfy" hai.
Teen coloured dots teen answer-classes hain. Pink dot (far left) zero-size request hai (C3, C7) — allocator NULL ya freeable non-NULL answer de sakta hai. Blue dot (middle) normal success hai (C1 malloc, C2 calloc). Yellow dot (right) itni badi request hai ki allocator give up karta hai aur NULL return karta hai (C4).
Do arrows above the line realloc hain: blue arrow right point karta hai (grow, C5) aur pink arrow left point karta hai (shrink, C6) — realloc literally is size axis par movement hai.
Axis ke neeche ek small fold-under lane hai jo hidden fourth axis hai: yeh dikhata hai ki ek bada request n × size wrap around ho ke zero ke paas aa jaata hai. Isliye jo request "yellow dot par land honi chahiye" woh secretly pink dot ke paas land kar sakti hai ek far too small block ke saath — yahi khatra C4 aur C9 warn karte hain.
Worked example Example 1 — C1: normal
malloc, success
Statement. n = 6 doubles ke liye room allocate karo, har slot i ko i * 1.5 se fill karo, slot 4 print karo, phir clean up karo. Assume karo sizeof(double) == 8 bytes.
Forecast: Call kitne bytes reserve karti hai, aur arr[4] kya hai? Padhne se pehle guess karo.
int n = 6 ;
double * arr = malloc (n * sizeof ( * arr)); // Step 1
if (arr == NULL ) return 1 ; // Step 2
for ( int i = 0 ; i < n; i ++ ) // Step 3
arr [i] = i * 1.5 ;
printf ( " %f\n " , arr [ 4 ]); // Step 4
free (arr); // Step 5
arr = NULL ;
Byte count compute karo. n * sizeof(*arr) = 6 * 8 = 48 bytes.
Yeh step kyun? malloc bytes mein count karta hai, objects mein nahi. sizeof(*arr) poochta hai "woh cheez kitni badi hai jis par arr point karta hai?" — yahan ek double, toh 8.
NULL check karo. Yeh step kyun? Heap full ho sakta hai; NULL pointer ko touch karna undefined behavior hai.
Initialize karo. Yeh step kyun? malloc aapko garbage deta hai — likhne se pehle padhna undefined hai.
Slot 4 padho. arr[4] = 4 * 1.5 = 6.0.
free phir null. Yeh step kyun? Warna memory leak ; nulling dangling pointer ko khatam karta hai.
Verify: bytes = 6 × 8 = 48 . Value = 4 × 1.5 = 6.0 . Units check: "objects × bytes/object = bytes." ✓
Worked example Example 2 — C2: zeroed counters ke liye
calloc (aur iska apna overflow)
Statement. Count karo ki har vowel-position kitni baar aati hai; aapko 5 counters chahiye (a e i o u) jo sab 0 se shuru hon, phir position 2 do baar increment karo. c[2] kya hai aur kisi bhi increment se pehle saare counters kya the?
Forecast: malloc ke saath starting values garbage hongi. calloc kya guarantee karta hai — aur kya woh apne multiply ko protect karta hai?
int * c = calloc ( 5 , sizeof ( * c)); // Step 1: 5 zeroed ints reserve karo
if ( ! c) return 1 ; // Step 2: NULL check karo
c [ 2 ] ++ ; c [ 2 ] ++ ; // Step 3: do increments
free (c);
Step 1 — two-argument call. calloc(5, sizeof(*c)) alag se count = 5 aur size = sizeof(*c) leta hai; woh inhe aapke liye total byte count nikaalने ke liye multiply karta hai , aur har jagah zeros likhta hai. Bytes reserved = 5 × sizeof(int) = 5 × 4 = 20 .
Yeh step kyun? Ek frequency table 0 se shuru honi chahiye; calloc yeh guarantee karta hai, jabki malloc garbage deta.
Step 2 — NULL check karo. Yeh step kyun? Jaisa hamesha: heap exhausted ho sakta hai, aur NULL ko touch karna undefined behavior hai.
Step 3 — do baar increment karo. Starting value guaranteed 0 hai, toh do ++ ke baad value 2 hai.
calloc ke multiply ke baare mein ek precision warning. Yeh sochna tempting hai ki calloc(count, size) guaranteed overflow-safe hai kyunki woh count * size multiply internally karta hai. Yeh C standard ke zariye guaranteed nahi hai — kya calloc detect karta hai ki count * size ek size_t overflow karta hai yeh implementation-defined hai. Zyaadatar real libcs (glibc, musl) overflow par NULL do return karte hain, lekin ek conforming-but-minimal implementation ko zaroorat nahi. Portable code ke liye, product khud validate karo (count > SIZE_MAX / size) exactly jaise Example 4 mein, call karne se pehle.
Verify: har counter 0 se shuru hota hai; c[2] = 0 + 1 + 1 = 2. Bytes reserved = 5 × 4 = 20 (4-byte int ke saath). ✓
Worked example Example 3 — C3:
zero-size request malloc(0)
Statement. void *p = malloc(0); aapko kya deta hai, aur kya free(p) legal hai?
Forecast: Kya p hamesha NULL hai? Kya aap ise dereference kar sakte ho?
Standard kya kehta hai. malloc(0) ya NULL ya ek unique non-NULL pointer return kar sakta hai jise aap dereference nahi karna lekin free ko pass kar sakte ho .
Yeh step kyun? Result implementation-defined hai — aap ek branch assume nahi kar sakte.
Isliye safe pattern: return ko kisi bhi allocation ki tarah treat karo — check karo, kabhi bhi read/write mat karo iske through, aur hamesha free karo.
Yeh step kyun? Agar non-NULL hai, toh free skip karna leak karta hai; agar NULL hai, toh free(NULL) ek harmless no-op hai.
Verify (logical): free(NULL) kuch nahi karta, aur malloc(0) ka koi bhi non-NULL result free ka valid argument hai. Toh free(malloc(0)); dono allowed outcomes mein safe hai. ✓ (Koi number plug karne ke liye nahi — yeh degenerate cell hai.)
malloc(0) NULL return karta hai, toh main free skip kar sakta hoon."
Yeh kyun sahi lagta hai: kai libraries NULL do return karti hain. Reality: doosri ek real, freeable pointer deti hain — tab free skip karna leak karta hai. Fix: jo bhi malloc return kare usse hamesha free karo.
Worked example Example 4 — C4: allocation
failure aur byte-count overflow
Statement. Aap ek absurd amount maangte ho, malloc NULL return karta hai. Lekin ek subtler trap hai: multiplication n * sizeof(*a) khud wrap around ho sakti hai malloc ke dekhne se pehle. Suppose karo intended request n = 1000000000 ints hai.
Forecast: Agar aap NULL check skip karo aur a[0] = 1 likho, kya hoga? Aur kya agar n * 4 us type mein overflow ho jisme compute ho raha hai?
size_t n = 1000000000 ; // Step 0 — sizes ke liye size_t use karo
if (n > SIZE_MAX / sizeof ( int )) return 1 ; // Step 1 — overflow guard
int * a = malloc (n * sizeof ( * a)); // Step 2 — may still fail
if (a == NULL ) { // Step 3 — check karo
fprintf (stderr, "out of memory \n " );
return 1 ;
}
a [ 0 ] = 1 ; // sirf success par pahuncha
free (a);
size_t use karo, long nahi. Yeh step kyun? Object sizes aur malloc ka argument size_t hain (upar callout mein define kiya gaya). Kuch platforms par long 32-bit hai, toh long n = 1000000000L; n * sizeof(*a) ek 32-bit type mein compute hoga aur ek tiny wrong value par wrap ho jaayega — malloc tab ek far too small block return karta aur a[i] writes heap corrupt karti.
Multiply guard karo. Yeh step kyun? n * sizeof(int) exactly tab overflow hota hai jab n > SIZE_MAX / sizeof(int). SIZE_MAX ko element size se divide karna aapko sabse bada n batata hai jo overflow nahi kar sakta ; multiply karne se pehle check karna hi portable tarika hai wrap catch karne ka — multiply silently ho jaane ke baad aap ise detect nahi kar sakte.
Attempt karo. Requested bytes = 1 0 9 × 4 = 4 × 1 0 9 (~4 GB). Heap exceed kar sakta hai → malloc NULL return karta hai.
Check karo. Yeh step kyun? Yeh aapke aur ek NULL dereference ke beech ka guard hai; ise skip karna a[0] = 1 ko NULL ke through likhwata hai → undefined behavior .
Verify: intended bytes = 1000000000 × 4 = 4000000000 . 32-bit size_t (SIZE_MAX = 4294967295) par overflow threshold hai SIZE_MAX / 4 = 1073741823; kyunki n = 1000000000 < 1073741823, product 4000000000 32 bits mein fit hota hai yahan — lekin us threshold se ऊपर koi bhi n wrap kar jaata, jise guard reject karta hai. ✓
Worked example Example 5 — C5:
realloc se grow , block moves
Statement. Aapke paas ek block mein 3 ints {7, 8, 9} hain. Ise 6 ints hold karne ke liye grow karo. Dikhao ki old contents survive karte hain aur pointer change ho sakta hai.
Forecast: Grow karne ke baad, kya v[1] abhi bhi 8 hai? Kya v abhi bhi same address hai?
int * v = malloc ( 3 * sizeof ( * v)); // Step 1
v [ 0 ] = 7 ; v [ 1 ] = 8 ; v [ 2 ] = 9 ;
int * tmp = realloc (v, 6 * sizeof ( * v)); // Step 2
if ( ! tmp) { free (v); return 1 ; } // Step 3
v = tmp; // Step 4
// v[0..2] abhi bhi 7,8,9 ; v[3..5] uninitialized
free (v);
3 se shuru karo. bytes = 3 × 4 = 12 .
realloc to 6. bytes = 6 × 4 = 24 . Allocator ya toh in place extend karta hai, ya ek naya 24-byte block allocate karta hai, 12 old bytes copy karta hai, aur old wala free karta hai.
Yeh step kyun? realloc hi woh single call hai jo pehle min(old,new) bytes preserve karte hue grow karta hai.
tmp use karo. Yeh step kyun? Failure par realloc NULL return karta hai lekin old block alive rakhta hai — seedha v ko assign karna iske liye single pointer kho deta (ek leak).
Commit karo. Old contents {7,8,9} intact hain; slots 3–5 garbage hain (uninitialized), toh padhne se pehle initialize karo.
Verify: preserved region size = min ( 12 , 24 ) = 12 bytes = 3 ints, toh v[0]=7, v[1]=8, v[2]=9 abhi bhi hold karte hain. New size = 24 bytes. ✓
Neeche ki figure exactly yeh move draw karti hai: upar wala blue block original 12 bytes hai jisme 7 8 9 hai; peela arrow dikhata hai ki woh 12 bytes naye 24-byte block mein copy ho rahe hain; right side ke teen pink cells fresh, uninitialized slots 3–5 hain.
Worked example Example 6 — C6:
realloc se shrink
Statement. Same block {7, 8, 9, 10, 11} (5 ints). Ise sirf 2 ints hold karne ke liye shrink karo. Kya survive karta hai, kya lost hota hai?
Forecast: 2 tak shrink karne ke baad, kya aap abhi bhi v[3] padh sakte ho?
int * v = malloc ( 5 * sizeof ( * v));
for ( int i = 0 ;i < 5 ;i ++ ) v [i] = 7 + i; // {7,8,9,10,11}
int * tmp = realloc (v, 2 * sizeof ( * v)); // 2 ints tak shrink karo
if (tmp) v = tmp; // shrink rarely fails, lekin guard karo anyway
// valid slots ab: v[0]=7, v[1]=8. v[2..4] GONE hain.
free (v);
2 tak shrink karo. new bytes = 2 × 4 = 8 . Allocator pehle min(old,new)=8 bytes rakhta hai.
Yeh step kyun? Shrinking sirf woh leading part preserve karta hai jiske liye aapne room rakha.
Kya lost hota hai. Shrink ke baad v[3] padhna ab undefined behavior hai — woh bytes ab aapki nahi hain.
Verify: preserved = min ( 20 , 8 ) = 8 bytes = 2 ints → v[0]=7, v[1]=8. Indices ≥ 2 8-byte block ke out of bounds hain. ✓
Worked example Example 7 — C7: teen
degenerate realloc edges
Statement. Precisely explain karo ki yeh kya karte hain:
realloc(NULL, 40), realloc(p, 0), aur realloc(NULL, 0).
Forecast: Kya yeh errors hain, ya yeh doosre functions ban jaate hain ?
realloc(NULL, 40). C standard kehta hai: "Agar ptr ek null pointer hai, toh realloc function specified size ke liye malloc function ki tarah behave karta hai." Toh yeh exactly malloc(40) hai — 40 fresh bytes.
Yeh step kyun? Yeh aapko ek grow-loop likhne deta hai jo pehle (empty) allocation par bhi kaam karta hai, kyunki realloc(NULL, …) ise malloc ki tarah seed karta hai.
realloc(p, 0) (non-NULL p). Standard result ko deliberately loose banata hai: ya toh woh old object free karta hai aur null pointer return karta hai, ya ek non-null pointer return karta hai jise aap object access karne ke liye use nahi kar sakte (size 0 ka koi object access karne ke liye nahi hai) — aap ise sirf free ko pass kar sakte ho. C23 mein realloc(p, 0) ko undefined behaviour bhi declare kiya gaya hai agar aap ise free ke roop mein rely karo. Practical rule: kabhi bhi realloc(p, 0) ko free karne ke tarike ke roop mein use mat karo; explicitly free(p) call karo.
Yeh step kyun? Kyunki return portably na ek usable pointer hai aur na portably ek free hai, in meanings ko mix karna exactly woh trap hai — toh hum ise sidestep karte hain.
realloc(NULL, 0) (pointer NULL aur size 0 dono). Rule 1 ke zariye (ptr null hai → malloc ki tarah behave karta hai) size 0 ke saath, yeh malloc(0) mein reduce ho jaata hai — yeh NULL ya ek unique non-NULL freeable pointer return kar sakta hai, aur aap ise exactly Example 3 ki tarah handle karte ho.
Yeh step kyun? Yeh dono degenerate axes ka intersection hai ek saath, toh dono edge rules saath apply hote hain — kabhi NULL assume mat karo.
Verify (logical): realloc(NULL, 40) ≡ malloc(40) → 40 bytes. realloc(p,0) ya NULL ya ek sirf free ke liye usable pointer return karta hai; realloc(NULL,0) ≡ malloc(0), jiska result implementation-defined hai. Toh portable rule "return check karo, free use karo free karne ke liye" teeno ke liye hold karta hai. ✓
Worked example Example 8 — C8:
word problem — unknown number of ints padhna
Statement. Ek file mein unknown count ke integers hain, ek per line. Unhe sab ek dynamically grown array mein padho, phir count aur sum report karo. Suppose karo file mein 10, 20, 30, 40, 50 (5 numbers) hain.
Forecast. Starting capacity 2 se, 5 items fit karne ke liye kitne doublings hote hain, aur sum kya hai?
int cap = 2 , len = 0 , x;
int * v = malloc (cap * sizeof ( * v)); // buffer seed karo
long sum = 0 ;
while ( scanf ( " %d " , & x ) == 1 ) { // input khatam hone tak padho
if (len == cap) { // Step A: buffer full?
cap *= 2 ; // Step B: capacity double karo
int * tmp = realloc (v, cap * sizeof ( * v)); // Step C: safe grow
if ( ! tmp) { free (v); return 1 ; }
v = tmp;
}
v [len ++ ] = x; // Step D: store + len advance karo
sum += x;
}
printf ( "count= %d sum= %ld\n " , len, sum);
free (v);
Step A — len == cap kyun check karo? len kitne ints aapne store kiye hain; cap kitne ke liye aapke paas room hai. Aap sirf tab grow karne ka cost pay karte ho jab used count allocated count ke barabar ho jaata hai — kabhi pehle nahi.
Step B — Double kyun karo (+1 nahi)? Geometric growth saare n inserts mein total copying cost O ( n ) amortized banata hai; har baar 1 add karna har insert par poori array recopy karta, O ( n 2 ) deta.
Step C — tmp + free(v) on failure kyun? Agar realloc NULL return karta hai toh old block abhi bhi valid hai; sirf tmp ke through commit karna ensure karta hai ki failure kabhi bhi old pointer ko orphan nahi karta (no leak), aur success par v = tmp (possibly moved) block adopt karta hai.
Step D — v[len++] = x kyun? Agli free slot mein store karo, phir len advance karo taaki agla insert ek aage land kare. sum += x chalte chalte tally karta hai.
Capacity trace (KAISA DIKHTA HAI): item 1 insert karo → len 1 ≤ cap 2; item 2 → len 2 == cap 2 full. Item 3 aata hai → cap 4 tak double karo, store karo. Item 4 → len 4 == cap 4 full → next... item 5 aata hai → cap 8 tak double karo, store karo. Toh capacity gayi 2 → 4 → 8: do doublings , final cap = 8 ≥ 5.
Verify: cap 2 se 5 items hold karne ke liye doublings: 2 → 4 → 8 = 2 doublings; final cap = 8 \ge 5. count = len = 5. sum = 10+20+30+40+50 = 150. ✓
Worked example Example 9 — C9:
exam twist — bug dhundho
Statement. Yeh code v grow karna aur kabhi leak nahi karna chahta hai. Do bugs chuppe hain. Unhe dhundho aur n = 100 ints ke liye corrected byte count do.
// BUGGY
int * v = malloc ( 4 ); // (a) sirf 4 bytes — ek int, bahut saare nahi
v = realloc (v, 100 * sizeof ( * v)); // (b) self-assign realloc
Forecast: Kaunsi line failure par leak karti hai, aur request kitni badi honi chahiye ?
Bug (a): hard-coded, wrong initial size. malloc(4) 4 bytes = ek int ke liye room reserve karta hai, intended many ke liye nahi. Byte counts derive kiye jaane chahiye: malloc(n * sizeof(*v)). Aur agar n input se aata hai, toh pehle n > SIZE_MAX / sizeof(*v) guard karo (Example 4) taaki multiply wrap na ho sake.
Yeh step kyun? Count derive karna line ko sahi rakhta hai agar type change ho, aur guard hidden fourth-axis overflow ko khatam karta hai.
Bug (b): self-assign leak. v = realloc(v, …) v ko overwrite karta hai chahe realloc NULL return kare, old block ko orphan karta hai → leak. tmp ke saath fix karo:
int * tmp = realloc (v, 100 * sizeof ( * v));
if ( ! tmp) { free (v); return 1 ; }
v = tmp;
Yeh step kyun? Failure par old block abhi bhi valid hai; uska address v mein rakhna (tmp ke zariye) matlab hai aap abhi bhi free(v) kar sakte ho.
Correct byte count. 100 ints ke liye: 100 × 4 = 400 bytes.
Yeh step kyun? Yeh woh value hai jo corrected malloc/realloc ko request karni chahiye.
Verify: intended bytes = 100 × 4 = 400 . Self-assign version exactly old block leak karta hai jab bhi realloc fail ho; tmp pattern us block ko reachable rakhta hai toh free(v) abhi bhi kaam karta hai. ✓
Recall (C3)
malloc(0) ke do legal outcomes kya hain?
Ya NULL, ya ek unique non-NULL pointer jise aap dereference nahi kar sakte lekin free kar sakte ho. Hamesha result free karo.
Recall (C2/C4)
SIZE_MAX kya hai, aur aap count * size ko overflow se kaise guard karte ho?
SIZE_MAX sabse badi value hai jo ek size_t hold kar sakta hai. if (count > SIZE_MAX / size) ... se guard karo multiply karne se pehle — wrap hone ke baad detect nahi ho sakta. calloc ka apna overflow check sirf implementation-defined hai, guaranteed nahi.
Recall (C4)
malloc fail hone ke baad pointer ki value kya hoti hai, aur sizes size_t mein kyun compute karo?
Failure ke baad pointer exactly NULL (integer 0) hai — hamesha pehle check karo. size_t use karo (long nahi) taaki n * sizeof(*p) usi width mein compute ho jo malloc expect karta hai aur 32-bit long platforms par silently wrap na ho.
Recall (C5 vs C6)
realloc ke baad kitne bytes of old data guaranteed intact hain?
min(old_size, new_size) bytes. Growing sab kuch rakhta hai; shrinking sirf leading kept region rakhta hai.
Recall (C7)
realloc(NULL, n) aur realloc(NULL, 0) kya karte hain?
realloc(NULL, n) malloc(n) ki tarah behave karta hai — ek fresh allocation. realloc(NULL, 0) malloc(0) mein reduce ho jaata hai — NULL ya ek freeable non-NULL pointer return kar sakta hai.
Recall (C8) Capacity 2 se start karke double karte hue, 5 items fit karne ke liye kitne doublings?
Do: 2 → 4 → 8.