5.1.29 · D4 · HinglishC Programming

ExercisesVariadic functions — va_list, va_start, va_arg, va_end

2,515 words11 min read↑ Read in English

5.1.29 · D4 · Coding › C Programming › Variadic functions — va_list, va_start, va_arg, va_end

Yahan sab kuch chaar ideas par tikaa hai jo tum pehle se jaante ho: chaar macros, stop-signal (count ya sentinel), default argument promotions, aur yeh fact ki galat promise karna undefined behavior hai. Hum kuch bhi naya build karne se pehle uska naam lete hain.


Level 1 — Recognition

Goal: kya tum variadic code padh kar uske pieces pehchaan sakte ho?

Exercise 1.1

Neeche di gayi function mein, <stdarg.h> ke chaar roles mein se har ek ko name karo jo dikhai de rahe hain, aur batao ki anchor (aakhri named parameter) kya hai.

int biggest(int count, ...) {
    va_list ap;
    va_start(ap, count);
    int best = va_arg(ap, int);
    for (int i = 1; i < count; i++) {
        int x = va_arg(ap, int);
        if (x > best) best = x;
    }
    va_end(ap);
    return best;
}
Recall Solution 1.1
  • va_list ap;cursor declare karta hai jo extra arguments ko walk karta hai.
  • va_start(ap, count); — cursor ko anchor ke baad point karta hai.
  • Anchor (last named parameter) count hai.
  • va_arg(ap, int) — next argument ko int ki tarah padhta hai aur advance karta hai.
  • va_end(ap); — return karne se pehle mandatory cleanup.

Note karo ki code total count values padhta hai: ek loop se pehle, phir count-1 loop ke andar. biggest(3, 7, 2, 9) ke liye answer 9 hai.

Exercise 1.2

In mein se kaun si signatures variadic function ke liye legal starting hain, aur kyun?

(a) void log(const char *fmt, ...);
(b) void log(...);
(c) int add(int a, int b, ...);
(d) double f(...) ;   // with no arguments ever passed
Recall Solution 1.2
  • (a) legal... se pehle ek named parameter fmt hai. ✔
  • (b) illegal — C mein ... se pehle at least one named parameter zaroori hai; va_start ke paas anchor karne ke liye kuch nahi. ✘
  • (c) legal — do named parameters bilkul theek hain; va_start(ap, b) aakhri ek b par anchor karta hai. ✔
  • (d) illegal — (b) jaisi hi wajah: koi named parameter exist hi nahi karta. ✘

Legal count: 2 (a aur c).


Level 2 — Application

Goal: sahi variadic code likho aur trace karo.

Exercise 2.1

int product(int n, ...) likho jo n integers ka product return kare. Phir product(4, 2, 3, 5, 1) trace karo aur result do.

Recall Solution 2.1
#include <stdarg.h>
int product(int n, ...) {
    va_list ap;
    va_start(ap, n);
    long p = 1;
    for (int i = 0; i < n; i++)
        p *= va_arg(ap, int);
    va_end(ap);
    return (int)p;
}

product(4, 2, 3, 5, 1) ka trace: 1·2 = 2, ·3 = 6, ·5 = 30, ·1 = 30. Result: 30.

Exercise 2.2

Neeche wala code doubles ka sum karta hai, lekin yeh broken hai. Bug dhundo aur fix karo. Phir fixed version ko avg(3, 1.5, 2.5, 4.0) par evaluate karo.

double avg(int n, ...) {
    va_list ap; va_start(ap, n);
    double s = 0;
    for (int i = 0; i < n; i++)
        s += va_arg(ap, float);   // <-- ?
    va_end(ap);
    return s / n;
}
Recall Solution 2.2

Bug: va_arg(ap, float). ... ko pass ki gayi float default argument promotions ki wajah se double mein promote ho jaati hai. Use float ki tarah wapas padhne se bytes galat interpret hoti hain → garbage (undefined behavior). Fix: va_arg(ap, double) use karo.

        s += va_arg(ap, double);

Fixed evaluation avg(3, 1.5, 2.5, 4.0): sum , phir . Result: 2.6666... (≈ 2.667).


Level 3 — Analysis

Goal: yeh reason karo ki kya galat hota hai aur kyun.

Exercise 3.1

Ek student sum(3, 10, 20) call karta hai — count 3 pass karta hai lekin sirf 2 values deta hai. Teesre va_arg par precisely kya hota hai yeh explain karo, aur kyun compiler ne ise catch nahi kiya.

Recall Solution 3.1

Loop teen baar chalta hai. Teesra va_arg(ap, int) memory ko aakhri real argument ke baad padhta hai — neeche figure dekho. Us jagah jo bhi bytes hain woh wahan hain (leftover stack, koi aur value, padding). Yeh undefined behavior hai: return hua "integer" garbage hai; program random number print kar sakta hai ya crash ho sakta hai.

Compiler ise catch nahi kar sakta kyunki count 3 ek ordinary runtime value hai. Compiler sirf yeh dekhta hai "kuch ints pass ho rahe hain"; use pata hi nahi kitne hain, kyunki ... koi type ya count information carry nahi karta. Saari safety tumhari zimmedari hai.

Figure — Variadic functions — va_list, va_start, va_arg, va_end

Exercise 3.2

Mixing bug consider karo: ek function sequence int, double, int expect karta hai lekin loop teen va_arg(ap, int) calls karta hai. Ek typical 64-bit ABI par jahan int 4 bytes aur double 8 bytes hai, cursor model use karke conceptually explain karo ki doosra aur teesra read kyun galat hain.

Recall Solution 3.2

Cursor us type ke size se aage badhta hai jo tum claim karte ho, na ki us type se jo actually wahan hai.

  • Read #1 va_arg(ap, int) — sahi hai; pehla slot sach mein int hai, cursor int ki width se aage badh jaata hai.
  • Read #2 va_arg(ap, int) — asli value ek double hai (zyada wide). int padhna double ki bytes ka sirf ek hissa grab karta hai → galat value, aur cursor bahut kam advance hota hai, ab woh double ke beech mein point kar raha hai.
  • Read #3 va_arg(ap, int) — misaligned position se shuru hota hai, leftover double bytes aur asli teesre int ka blend padhta hai → garbage.

Moral: ek galat type baad ke har read ko desynchronise kar deta hai. Cursor arithmetic ([[Calling Conventions and the Stack|stack layout]] dependent) tabhi kaam karta hai jab har promised type pushed, promoted type se match kare.


Level 4 — Synthesis

Goal: pieces ko combine karke ek chhota real feature banao.

Exercise 4.1

int count_positive(int stop, ...) likho jo int arguments tab tak padhe jab tak sentinel value stop na dikhe, aur return kare ki sentinel se pehle padhi gayi values mein se kitni strictly zero se badi theen. Phir count_positive(-1, 5, -2, 0, 7, -1) evaluate karo.

Recall Solution 4.1
#include <stdarg.h>
int count_positive(int stop, ...) {
    va_list ap; va_start(ap, stop);
    int count = 0, x;
    while ((x = va_arg(ap, int)) != stop) {
        if (x > 0) count++;
    }
    va_end(ap);
    return count;
}

Yahan anchor stop sentinel definition ke roop mein bhi kaam karta hai. Caller ko list stop se zaroor khatam karni chahiye. count_positive(-1, 5, -2, 0, 7, -1) ka trace: 5 padho (>0 ✔ count=1), -2 (nahi), 0 (strictly >0 nahi, nahi), 7 (>0 ✔ count=2), -1 == stop → ruk jao. Result: 2.

Exercise 4.2

va_copy use karke double range_ratio(int n, ...) likho jo n doubles par kaam kare aur (max) / (average) return kare. Tumhe arguments par do baar pass karna hai (ek average ke liye, ek max ke liye). range_ratio(3, 2.0, 4.0, 6.0) evaluate karo.

Recall Solution 4.2
#include <stdarg.h>
double range_ratio(int n, ...) {
    va_list ap, ap2;
    va_start(ap, n);
    va_copy(ap2, ap);          // duplicate BEFORE consuming ap
 
    double sum = 0;
    for (int i = 0; i < n; i++) sum += va_arg(ap, double);
    va_end(ap);
    double avg = sum / n;
 
    double mx = va_arg(ap2, double);
    for (int i = 1; i < n; i++) {
        double v = va_arg(ap2, double);
        if (v > mx) mx = v;
    }
    va_end(ap2);
 
    return mx / avg;
}

va_copy kyun: ek va_list (potentially) single-use hoti hai aur ise rewind ya = se copy nahi kiya ja sakta. va_copy(ap2, ap) ek independent cursor banata hai taaki doosri walk pehle argument se phir shuru ho sake. range_ratio(3, 2.0, 4.0, 6.0) ka trace: sum , avg , max , ratio . Result: 1.5.


Level 5 — Mastery

Goal: design karo, safety ke baare mein reason karo, aur wide system se connect karo.

Exercise 5.1

Tum ek chhota logger void logf(const char *fmt, ...) likh rahe ho jo sirf %d (int) aur %s (string) support karta hai. Parsing loop sketch karo, aur explain karo ki ek line kaun si hai jise koi attacker exploit kar sakta hai agar fmt untrusted input se aaye. Vulnerability class ka naam batao.

Recall Solution 5.1
#include <stdarg.h>
#include <stdio.h>
void logf(const char *fmt, ...) {
    va_list ap; va_start(ap, fmt);
    for (const char *p = fmt; *p; p++) {
        if (*p != '%') { putchar(*p); continue; }
        p++;                                   // skip '%'
        if (*p == 'd')      printf("%d", va_arg(ap, int));
        else if (*p == 's') fputs(va_arg(ap, const char*), stdout);
        else                putchar(*p);       // literal, e.g. "%%"
    }
    va_end(ap);
}

Dangerous pattern yeh hai ki kuch aisa logf(user_string) call karo jahan user_string mein conversion specifiers hain lekin koi matching arguments pass nahi kiye. Tab har %d/%s real argument list se aage va_arg karta hai → stack memory padhta/print karta hai ya crash ho jaata hai. Yeh format string vulnerability class hai (real-world version printf(user_input) hai instead of printf("%s", user_input)). Safe call: logf("%s", user_string); — format ek fixed literal hai jo tum control karte ho; user data ek argument hai, format nahi.

Exercise 5.2

Parent note ke toy macros given hain:

#define my_va_arg(ap, T) (*(T*)((ap) += sizeof(T), (ap) - sizeof(T)))

Ek 64-bit ABI par (int = 4 bytes, double = 8 bytes, pointer = 8 bytes), cursor ap byte offset 0 se start karta hai. Maano promoted arguments contiguously laid out hain: int, double, char*. Inhe sahi promoted types se order mein padhne ke baad, ap kaunsa byte offset hold karta hai? Yeh naive model kaun si subtle real-world detail ignore karta hai?

Recall Solution 5.2

Har baar sizeof(claimed type) se advance karke:

  • my_va_arg(ap, int) ke baad: offset
  • my_va_arg(ap, double) ke baad: offset
  • my_va_arg(ap, char*) ke baad: offset

Final offset: 20.

Yeh kya ignore karta hai: alignment aur register passing. Real ABIs require kar sakte hain ki double 8-byte-aligned offset par ho (padding insert karke, to arithmetic plain sum nahi hoti), aur kai arguments stack par nahi balki registers mein aate hain. Yahi wajah hai ki real <stdarg.h> compiler-magic black box hai aur toy macro nahi — calling conventions dekho.


Recall Har level ka ek-line recap

L1 pieces aur anchor pehchano · L2 likho aur promotion bugs fix karo · L3 explain karo ki over-reads aur type-mismatches kyun desynchronise karte hain · L4 sentinels aur va_copy se build karo · L5 security aur ABI reality se connect karo.

Related: C Standard Library · Function Pointers in C · Undefined Behavior in C · Format String Vulnerabilities.