5.1.29 · D3 · HinglishC Programming

Worked examplesVariadic functions — va_list, va_start, va_arg, va_end

4,139 words19 min read↑ Read in English

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 — Variadic functions — va_list, va_start, va_arg, va_end

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 = 3 hai. Compiler ne ise name diya, isliye va_start iska 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 ko sizeof(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;
}
  1. va_start(ap, n) — cursor ko named n ke ठीk baad anchor karo (memory picture mein magenta arrow). Yeh step kyun? n akela 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. n woh anchor hai.
  2. Loop i = 0 → 3, har baar va_arg(ap, int) — box pull karo, advance karo (violet arrow, repeat). Yeh step kyun? n == 4 exactly chaar boxes ka promise karta hai. Hum exactly chaar padhte hain; zyada nahi (woh cell I hoga), kam nahi.
  3. long total mein accumulate karo. Yeh step kyun? Chaar int generally ek 32-bit int overflow kar sakte hain; ek long accumulator ek sasta safety margin hai.
  4. 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?

  1. va_start(ap, n) with n == 0. Yeh step kyun? va_start sirf n ke 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.
  2. Loop 0 baar chalta hai — condition 0 < 0 immediately false hai. Yeh step kyun? Count khud humein guard karta hai. Hum kabhi va_arg call nahi karte, toh kabhi n se aage memory touch nahi karte. Yahi toh count ka poora point hai: degenerate case khud handle ho jaata hai.
  3. va_end(ap) — abhi bhi zaroori. Yeh step kyun? "Har va_start ka ek matching va_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;
}
  1. first anchor 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.
  2. Loop const char* padhta rehta hai jab tak NULL na mile. Yeh step kyun? Koi count pass nahi kiya gaya, isliye last box ke contents (ek null pointer) orange stop signal hai. Caller ko woh NULL zaroor dena chahiye ya hum end se baahar nikal jaate hain (cell I).
  3. Pointer type match karni chahiye: va_arg(ap, const char*). Yeh step kyun? Humne API design karte waqt pointers ka promise kiya tha; unhe int ki 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.

  1. Caller 'A' (ek char) pass karta hai. Kyun matter karta hai: upar ke rule se, belt par ek 4-byte int box (value 65) place hota hai, nahi ek 1-byte char box. Memory picture mein, har belt box kam se kam int-wide hai.
  2. 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.
  3. 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?

  1. Caller 1.5f, 2.5f pass karta hai (dono float). Kyun matter karta hai: promotion se, har belt par ek 8-byte double ban jaata hai.
  2. 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 ek double ka bit pattern ka aadha hissa float ki tarah padhte ho.
  3. 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;
}
  1. 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.
  2. '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. Exact printf letters use karne se koi guesswork nahi rehti.
  3. Truncation (long)3.14 = 3 deliberate 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
}
  1. va_copy(ap2, ap) ap ko touch karne se pehle. Yeh step kyun? Jab va_arg ap advance kar deta hai, copy already start position par exist karni chahiye (dono cursors magenta arrow par). Pehle copy, baad mein consume.
  2. Pehla pass ap ke saath: n baar loop karo har int sum karte hue, phir mean = sum / n compute 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.
  3. va_end(ap) pehle pass ke turant baad. Yeh step kyun? ap ab fully consumed aur useless hai; ise khatam karo jaise hi iska kaam ho gaya taaki koi dangling cursor na linge.
  4. Doosra pass untouched ap2 ke saath: n baar loop karo, mean subtract karo, square karo, accumulate karo. Yeh step kyun? Yahi va_copy ka poora reason tha — ap2 abhi bhi magenta arrow par baitha hai, isliye hum wohi belt dobara walk kar sakte hain squared deviations compute karne ke liye.
  5. va_end(ap2) phir sq / n return karo. Yeh step kyun? Do origins (ap ke liye va_start, ap2 ke liye va_copy) do va_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;
}
  1. Do named params, tag aur n. Yeh step kyun? va_start last named parameter par anchor karta hai — yahan n par, tag par nahi. Magenta arrow n ke baad land karni chahiye; yeh galat karo aur cursor galat jagah se start hoga.
  2. Har ek ko double ki tarah padho (cell E promotion). Yeh step kyun? Temperatures float literals ke roop mein pass ho sakti hain; belt par woh double hain.
  3. Zero-guard n par (cell B) divide-by-zero rokata hai. Yeh step kyun? Ex 2 se degenerate-case protection reuse karna matlab log_avg("core", 0) crash karne ki bajaye 0.0 return 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;
}
  1. 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).
  2. 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_arg ko 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.
  3. 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)

Active Recall