Worked examples — Variadic functions — va_list, va_start, va_arg, va_end
5.1.29 · D3· Coding › C Programming › Variadic functions — va_list, va_start, va_arg, va_end
Yeh page [[Variadic Functions — va_list, va_start, va_arg, va_end|parent topic]] ka exhaustive drill room hai. Parent ne tumhe chaar macros aur teen examples dikhaye. Yahan hum har tarah ki situation enumerate karte hain jo ek variadic function face kar sakta hai — phir har cell ke liye ek worked example walk karte hain taaki tum kisi bhi unseen case se kabhi na milo.
Neeche sab kuch assume karta hai ki tum already chaar cursor macros se mil chuke ho. Agar koi fuzzy lage, pehle parent dobara padho. Jo nayi tools yahan aati hain (promotion rules, stack layout) — unhe scratch se wahin build kiya gaya hai jahan zaroori ho.
Pehle: belt actually kya hai (memory ka picture)
Kisi bhi example se pehle, "conveyor belt" ko concrete banate hain, kyunki neeche har case asliyat mein memory ke baare mein ek statement hai jo address order mein laid out hai. Yeh stack-layout tool hai, zero se build kiya gaya.

Figure — alt text aur caption. Ek left-to-right memory diagram. Bilkul left mein, ek filled peach box jis par "n = 3 (named)" likha hai, named parameter hai. Uske right mein teen outlined boxes hain jis mein se har ek par "int, 4 bytes" likha hai aur 10, 20, 30 values hain — variadic belt. Neeche ek navy arrow right ko point karta hai: "increasing address". Ek magenta arrow named box ke ठीक baad ke gap par girta hai: "va_start(ap, n) lands here". Ek violet arrow pehle box se right ki taraf span karta hai: "va_arg(ap,int): read + advance". Teesre box ke baad ek orange arrow upar "STOP: count or NULL" ki taraf point karta hai. Orange stop ke baad, ek italic magenta label "? garbage (UB)" padhta hai jo undefined memory mark karta hai. Picture yeh kehti hai: cursor magenta arrow par janam leta hai, violet arrow ke neeche box-by-box chalta hai, aur zaroori hai ki orange stop par ruke — usse aage sab ? region hai.
Figure ko left se right padho jaise increasing memory address:
- Left par peach box named parameter
n = 3hai. Compiler ne ise name diya, isliyeva_startiska address dhundh sakta hai. - Teen outlined boxes variadic arguments hain, har ek full
int(4 bytes) — width note karo, yeh promotion traps (cells D, E) ke liye matter karega. - Magenta arrow woh jagah hai jahan
va_start(ap, n)cursor drop karta hai: named box ke ठीk baad. - Violet arrow ek
va_arg(ap, int)dikhata hai: cursor ke neeche box padho, phir cursor kosizeof(int)aage slide karo. - Orange arrow woh akela cheez hai jo humein bachata hai: ek count ya sentinel jo humein bolta hai ki pehle ruko cursor
?region mein slide kare — woh undefined memory jo hamari nahi hai.
Yeh picture saari nau examples ke liye yaad rakho: har bug cursor ka galat box mein, galat width par, ya orange stop ke baad land karna hai.
Scenario matrix
Har sawal jo hum pooch sakte hain woh reduce hota hai: kitne boxes, har mein kya, aur hum kab rokna jaante hain? Neeche matrix har distinct case-class list karta hai. Baaki page har cell ko ek fully worked example se bharta hai. (Plain-text form: A = count/int, B = zero args, C = sentinel/pointers, D = char promotion, E = float promotion, F = format-string types, G = re-scan, H = word problem, I = over-read UB.)
| # | Case class | Twist jo yeh test karta hai | Example |
|---|---|---|---|
| A | Count method, sab int |
Sabse simple: caller kehta hai "n follow karte hain" | Ex 1 |
| B | Zero variadic args (degenerate) | Belt empty hai — kya va_start/va_end phir bhi kaam karta hai? |
Ex 2 |
| C | Sentinel method, pointers | Koi count nahi; ek NULL end mark karta hai |
Ex 3 |
| D | Promotion trap: char/short |
Chhote integers int mein widened hokar aate hain |
Ex 4 |
| E | Promotion trap: float |
Floats double mein widened hokar aate hain |
Ex 5 |
| F | Mixed types driven by a format string | Har box ka type runtime par ek leading string se decide hota hai | Ex 6 |
| G | Re-scan with va_copy |
Ek hi belt do baar padho | Ex 7 |
| H | Word problem (real-world logging) | Upar sab apply karo context mein | Ex 8 |
| I | Exam twist: ek zyada padhna | [[Undefined Behavior in C | UB]] boundary |
Ex 1 — Cell A: count method, sab int
Forecast: aage padhne se pehle total guess karo. (Unhe jodo: , yaani 5 plus 15 plus 25 plus 55.)
int sum(int n, ...) {
va_list ap;
va_start(ap, n);
long total = 0;
for (int i = 0; i < n; i++)
total += va_arg(ap, int);
va_end(ap);
return (int)total;
}va_start(ap, n)— cursor ko namednke ठीk baad anchor karo (memory picture mein magenta arrow). Yeh step kyun?nakela woh cheez hai jise compiler ne naam diya; belt ka baaki hissa unnamed hai, isliye cursor ko batana padta hai ki unnamed part kahan shuru hota hai.nwoh anchor hai.- Loop
i = 0 → 3, har baarva_arg(ap, int)— box pull karo, advance karo (violet arrow, repeat). Yeh step kyun?n == 4exactly chaar boxes ka promise karta hai. Hum exactly chaar padhte hain; zyada nahi (woh cell I hoga), kam nahi. long totalmein accumulate karo. Yeh step kyun? Chaarintgenerally ek 32-bitintoverflow kar sakte hain; eklongaccumulator ek sasta safety margin hai.va_end(ap)phir return.
Verify: (five plus fifteen plus twenty-five plus fifty-five equals one hundred). Read ke baad cursor address hai start + k*sizeof(int); 4 reads ke baad yeh last argument ke ek int baad point karta hai — exactly orange stop par.
Ex 2 — Cell B: zero variadic arguments (degenerate)
Forecast: kya yeh crash hoga, garbage return karega, ya 0 return karega?
va_start(ap, n)withn == 0. Yeh step kyun?va_startsirfnke baad address chahiye; yeh koi variadic arg nahi padhta. Toh yeh perfectly legal hai even jab belt empty ho — magenta arrow right orange stop par land karta hai.- Loop
0baar chalta hai — condition0 < 0immediately false hai. Yeh step kyun? Count khud humein guard karta hai. Hum kabhiva_argcall nahi karte, toh kabhinse aage memory touch nahi karte. Yahi toh count ka poora point hai: degenerate case khud handle ho jaata hai. va_end(ap)— abhi bhi zaroori. Yeh step kyun? "Harva_startka ek matchingva_end" wala rule empty lists ke liye koi exception nahi rakhta.
Verify: empty sum (zero). Importantly: beech mein zero va_arg calls ke saath va_start+va_end well-defined hai — kuch nahi padhna safe hai; sirf zyada padhna UB hai.
Ex 3 — Cell C: sentinel method, pointers
Forecast: string lengths (2 plus 3 plus 1) sum karo — lekin count kahan se aata hai?
size_t total_len(const char *first, ...) {
va_list ap;
va_start(ap, first);
size_t len = strlen(first);
const char *s;
while ((s = va_arg(ap, const char*)) != NULL)
len += strlen(s);
va_end(ap);
return len;
}firstanchor bhi hai aur count bhi (strlen("ab") = 2). Yeh step kyun? List mein naturally ek pehla element hota hai; ise named banana do kaam ek saath karta hai — anchor aur pehla datum.- Loop
const char*padhta rehta hai jab takNULLna mile. Yeh step kyun? Koi count pass nahi kiya gaya, isliye last box ke contents (ek null pointer) orange stop signal hai. Caller ko wohNULLzaroor dena chahiye ya hum end se baahar nikal jaate hain (cell I). - Pointer type match karni chahiye:
va_arg(ap, const char*). Yeh step kyun? Humne API design karte waqt pointers ka promise kiya tha; unheintki tarah padhna bytes ko misinterpret karega.
Verify: (two plus three plus one equals six). NULL box failing while test se consume hota hai aur add nahi hota — sahi hai.
Ex 4 — Cell D: char/short promotion trap
Forecast: guess karo ki char ki tarah padhne se (65 plus 66 plus 67) milega ya garbage.
Yahan tool build karna padega: default argument promotions.
- Caller
'A'(ekchar) pass karta hai. Kyun matter karta hai: upar ke rule se, belt par ek 4-byteintbox (value 65) place hota hai, nahi ek 1-bytecharbox. Memory picture mein, har belt box kam se kamint-wide hai. va_arg(ap, char)undefined hai — tumne cursor ko bola ki 1 byte padho aur 1 byte advance karo, lekin box 4 bytes wide hai. Yeh step kyun? Size mismatch cursor corrupt karta hai: pehle "read" ke baad, violet arrow box 1 ke beech mein land karta hai, box 2 ke start par nahi.- Fix:
va_arg(ap, int). Yeh step kyun? Box ko us width par padho jis par woh actually stored tha —int.
int sum_bytes(int n, ...) {
va_list ap; va_start(ap, n);
long t = 0;
for (int i = 0; i < n; i++) t += va_arg(ap, int); // int, char nahi!
va_end(ap);
return (int)t;
}Verify: sum_bytes(3, 'A','B','C') (65 plus 66 plus 67 equals 198) sahi int version ke saath.
Ex 5 — Cell E: float promotion trap
Forecast: aur (one-point-five aur two-point-five) ka average kya hai, aur konsa type padhna zaroori hai?
- Caller
1.5f,2.5fpass karta hai (donofloat). Kyun matter karta hai: promotion se, har belt par ek 8-bytedoubleban jaata hai. va_arg(ap, float)undefined hai — jahan 8 bytes stored hain wahan 4 bytes padhta hai. Yeh step kyun? Ex 4 jaise hi width mismatch — cursor desync ho jaata hai aur tum ekdoubleka bit pattern ka aadha hissafloatki tarah padhte ho.- Fix:
va_arg(ap, double).
double avg(int n, ...) {
va_list ap; va_start(ap, n);
double s = 0.0;
for (int i = 0; i < n; i++) s += va_arg(ap, double); // double, float nahi!
va_end(ap);
return n ? s / n : 0.0;
}Verify: (one-point-five plus two-point-five, sab over two, equals two). double ki tarah padho. n ? ... : 0.0 guard cell B reuse karta hai (zero args → 0.0 return karo, zero se divide nahi).
Ex 6 — Cell F: mixed types driven by a format string
Forecast: (42 plus 3 plus 'Z' ka ASCII code) guess karo.
Exactly yahi hai jaise printf runtime par types decide karta hai — format string type program hai. Hum deliberately printf ke conventions reuse karte hain taaki koi ambiguity na ho: 'd' ek decimal int hai, 'f' ek floating-point value hai (jo, promotion se, belt par double hai), aur 'c' ek character hai (int mein promoted). Yahi power format string vulnerabilities ka source bhi hai.
long describe(const char *fmt, ...) {
va_list ap; va_start(ap, fmt);
long acc = 0;
for (const char *p = fmt; *p; p++) {
switch (*p) {
case 'd': acc += va_arg(ap, int); break; // int
case 'f': acc += (long)va_arg(ap, double); break; // double, truncated
case 'c': acc += va_arg(ap, int); break; // char int mein promoted
}
}
va_end(ap);
return acc;
}- Format string ko char by char walk karo. Yeh step kyun? String khud count-aur-type table hai: iska length humein batata hai kitne boxes hain, har letter type batata hai. Alag count ki zaroorat nahi.
'd'→va_arg(ap, int),'f'→va_arg(ap, double),'c'→va_arg(ap, int)(char promoted!). Yeh step kyun? Har conversion promoted box width se match karni chahiye, cells D aur E ki echo. Exactprintfletters use karne se koi guesswork nahi rehti.- Truncation
(long)3.14 = 3deliberate hai.
Verify: 'Z' ka ASCII 90 hai. Checksum (42 plus 3 plus 90 equals 135).
Ex 7 — Cell G: re-scan with va_copy
Forecast: (two, four, six, eight) ka mean hai; deviations hain; variance guess karo.
Ek va_list aksar single-use hota hai — tum ise rewind nahi kar sakte aur = ise safely copy nahi kar sakta. Dobara padhne ke liye, humein va_copy chahiye.
long stats(int n, ...) {
va_list ap, ap2;
va_start(ap, n);
va_copy(ap2, ap); // ap consume karne se PEHLE duplicate karo
long sum = 0;
for (int i = 0; i < n; i++) sum += va_arg(ap, int);
va_end(ap);
double mean = (double)sum / n;
long sq = 0; // copy ka doosra pass
for (int i = 0; i < n; i++) {
long d = va_arg(ap2, int) - (long)mean;
sq += d * d;
}
va_end(ap2);
return sq / n; // population variance
}va_copy(ap2, ap)apko touch karne se pehle. Yeh step kyun? Jabva_argapadvance kar deta hai, copy already start position par exist karni chahiye (dono cursors magenta arrow par). Pehle copy, baad mein consume.- Pehla pass
apke saath:nbaar loop karo harintsum karte hue, phirmean = sum / ncompute karo. Yeh step kyun? Mean chahiye pehle ta ki hum measure kar sakein ki har value usse kitni door hai; isliye belt ka pehla walk sirf us center ko compute karne ke liye exist karta hai. va_end(ap)pehle pass ke turant baad. Yeh step kyun?apab fully consumed aur useless hai; ise khatam karo jaise hi iska kaam ho gaya taaki koi dangling cursor na linge.- Doosra pass untouched
ap2ke saath:nbaar loop karo, mean subtract karo, square karo, accumulate karo. Yeh step kyun? Yahiva_copyka poora reason tha —ap2abhi bhi magenta arrow par baitha hai, isliye hum wohi belt dobara walk kar sakte hain squared deviations compute karne ke liye. va_end(ap2)phirsq / nreturn karo. Yeh step kyun? Do origins (apke liyeva_start,ap2ke liyeva_copy) dova_ends demand karte hain — har cursor exactly ek baar end hota hai.
Verify: (two plus four plus six plus eight, over four, equals five). Deviations ; squares ; variance (twenty over four equals five).
Ex 8 — Cell H: real-world word problem (ek logger)
Forecast: (ninety-nine, one-hundred-point-five, one-hundred-four-point-five) ka average, aur kya yeh se zyada hai?
double log_avg(const char *tag, int n, ...) {
va_list ap;
va_start(ap, n); // LAST named param, n, par anchor karo
double sum = 0.0;
for (int i = 0; i < n; i++)
sum += va_arg(ap, double); // temps floating point hain → double
va_end(ap);
double avg = n ? sum / n : 0.0;
// if (avg > 100.0) raise_alarm(tag);
return avg;
}- Do named params,
tagaurn. Yeh step kyun?va_startlast named parameter par anchor karta hai — yahannpar,tagpar nahi. Magenta arrownke baad land karni chahiye; yeh galat karo aur cursor galat jagah se start hoga. - Har ek ko
doubleki tarah padho (cell E promotion). Yeh step kyun? Temperaturesfloatliterals ke roop mein pass ho sakti hain; belt par wohdoublehain. - Zero-guard
npar (cell B) divide-by-zero rokata hai. Yeh step kyun? Ex 2 se degenerate-case protection reuse karna matlablog_avg("core", 0)crash karne ki bajaye0.0return karta hai.
Verify: (ninety-nine plus one-hundred-point-five plus one-hundred-four-point-five, over three). Kyunki , alarm branch fire hota. tag sirf message ke liye use hota hai, kabhi va_arg ke zariye nahi padha jaata.
Ex 9 — Cell I: exam twist, ek zyada padhna
Forecast: kya 4th read 0 return karta hai, ya poora result meaningless hai?
int sum(int n, ...) {
va_list ap; va_start(ap, n);
long total = 0;
for (int i = 0; i <= n; i++) // BUG: <= n+1 boxes padhta hai
total += va_arg(ap, int);
va_end(ap);
return (int)total;
}- Pehle teen
va_arg(ap, int)teen real boxes padhte hain: (ten, twenty, thirty). Yeh step kyun? Woh boxes genuinely belt par exist karte hain (memory picture mein teen outlined boxes). - Chautha
va_arg(ap, int)cursor ko orange stop se aage slide karta hai,?region mein (pehle define ki gayi undefined memory mein). Yeh step kyun?va_argko yeh batane wala kuch nahi hai ki belt kahan khatam hoti hai. Box 3 ke baad woh hai jo stack ya register save area mein tha — ek return address, saved registers, junk. - Yeh undefined behavior hai — "zero return karta hai" nahi, "garbage-but-consistent return karta hai" nahi. Program galat number return kar sakta hai, crash ho sakta hai, ya luck se sahi lag sakta hai. Kabhi isa par rely mat karo.
Verify: sahi teen-box sum (loop i < n) hai (ten plus twenty plus thirty equals sixty). Buggy chauthe read ka koi defined value nahi hai, isliye iske liye koi checkable number exist nahi karta — yahi exactly lesson hai: cursor ko kabhi orange stop cross mat karne do.
Recall Kaun sa cell kaun sa tha?
Count-all-int ::: Ex 1 (A)
Empty belt, n == 0 ::: Ex 2 (B)
Sentinel NULL stop ::: Ex 3 (C)
char promotes to int ::: Ex 4 (D)
float promotes to double ::: Ex 5 (E)
Format string picks types ::: Ex 6 (F)
Two passes via va_copy ::: Ex 7 (G)
va_start last named param par anchor karta hai ::: Ex 8 (H)
End se aage padhna UB hai ::: Ex 9 (I)