Variadic functions — va_list, va_start, va_arg, va_end
WHY do we even need this?
The cost of this power: the compiler stops checking you. You alone decide how many args there are and what type each is. Promise wrong → undefined behavior.
WHAT are the four pieces?
HOW does it work, derived from first principles
When you call f(a, b, c, d), the arguments are placed somewhere the callee can reach (on many ABIs: the stack and/or registers, in a known order). The named parameters consume the first slots. The remaining bytes are still there but unnamed.

Worked Example 1 — sum of n integers (count method)
#include <stdarg.h>
#include <stdio.h>
int sum(int n, ...) { // n tells us HOW MANY follow
va_list ap;
va_start(ap, n); // cursor starts after 'n'
long total = 0;
for (int i = 0; i < n; i++)
total += va_arg(ap, int); // pull each as int
va_end(ap);
return (int)total;
}
int main(void){ printf("%d\n", sum(3, 10, 20, 30)); } // 60| Step | Code | Why this step? |
|---|---|---|
| 1 | int sum(int n, ...) |
Need a named param n so va_start has an anchor and so we know the count. |
| 2 | va_start(ap, n) |
Point cursor just after n — at the first ... argument. |
| 3 | va_arg(ap, int) in loop |
Read & advance, exactly n times; type int matches what we passed. |
| 4 | va_end(ap) |
Required cleanup before returning. |
Worked Example 2 — sentinel-terminated string concatenator
#include <stdarg.h>
#include <string.h>
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) // stop at NULL
len += strlen(s);
va_end(ap);
return len;
}
// total_len("ab", "cde", "f", NULL) == 6| Step | Why this step? |
|---|---|
first is named |
Gives the anchor; also it's natural here (a list always has a first item). |
loop until NULL |
No count was passed, so a sentinel marks the end. Caller must pass NULL. |
va_arg(ap, const char*) |
We promised pointers — type must match the args. |
Worked Example 3 — re-scanning with va_copy
double average(int n, ...) {
va_list ap, ap2;
va_start(ap, n);
va_copy(ap2, ap); // duplicate cursor BEFORE consuming
long sum = 0;
for (int i = 0; i < n; i++) sum += va_arg(ap, int);
va_end(ap);
// ... could re-walk ap2 for a second pass ...
va_end(ap2);
return n ? (double)sum / n : 0.0;
}Common Mistakes (Steel-man each)
Recall Feynman: explain to a 12-year-old
Imagine a conveyor belt carrying boxes, and you're standing at the start. You don't know how many boxes are coming. va_start says "go to the first box." va_arg(int) says "open the next box, expecting a number, take it, and step to the next box." But here's the catch: the belt doesn't tell you when it ends and the boxes aren't labeled with what's inside. So you must agree beforehand — either someone tells you "there are 3 boxes," or the last box is a special empty "STOP" box. If you guess the count or the contents wrong, you grab thin air. va_end is putting your hands down when you're done.
Active Recall
What header declares the variadic macros?
<stdarg.h>What is va_list?
What does va_start(ap, last) do?
ap to point just after the last named parameter last.What does va_arg(ap, type) do?
type, and advances the cursor.Why must you call va_end?
va_list before the function returns.Why must a variadic function have ≥1 named parameter?
va_start needs the name of the last named parameter as its anchor point.Two ways to know how many variadic args there are?
NULL/-1).What promotion happens to float arguments?
double, so use va_arg(ap, double).What promotion happens to char/short arguments?
int, so use va_arg(ap, int).What is va_copy(dst, src) for?
va_list so the arguments can be scanned more than once.Why does mismatching va_arg type cause UB?
Can you rewind a va_list?
va_copy first if you need a second pass.Connections
- C Standard Library —
printf/scanf/vfprintfare variadic. - Default Argument Promotions — why
char→int,float→double. - Calling Conventions and the Stack — how args are laid out for
va_argto walk. - Function Pointers in C — passing variadic functions around.
- Undefined Behavior in C — the dominant risk class here.
- Format String Vulnerabilities — security cost of unchecked variadic args.
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, variadic function matlab aisa function jiska argument count fixed nahi hota — jaise printf, jisme tum 1 ya 10 values pass kar sakte ho. Normal function me compiler har argument ke liye naam jaanta hai, par yahan ... (ellipsis) ke baad jitne bhi arguments aaye, unka koi naam nahi hota. Toh C tumhe <stdarg.h> ke 4 magic tools deta hai: va_list (ek cursor/pointer jaisa), va_start (cursor ko shuru karo, last named parameter ke baad), va_arg (agla argument nikalo, type batake), aur va_end (kaam khatam, cleanup).
Sabse important baat: compiler ab tumhari checking nahi karta. Tum khud decide karte ho kitne arguments hain aur har ek ka type kya hai. Isliye tumhe ya toh ek count pass karna padta hai (jaise sum(3, ...) me 3), ya ek sentinel rakhna padta hai (jaise list ke end me NULL). Galat count ya galat type diya — toh UB (undefined behavior), program crash ya garbage value.
Ek bada trap yaad rakho: default argument promotions. Jab tum char ya short pass karte ho, woh automatically int ban jaata hai, aur float ban jaata hai double. Isliye chahe tumne float diya ho, hamesha va_arg(ap, double) likho, aur chote integers ke liye va_arg(ap, int). Agar tum va_arg(ap, float) likhoge toh galat bytes read honge.
Yaad rakhne ka tareeka: S-A-E — Start, Arg (baar baar), End. Aur ek named parameter zaroori hai kyunki va_start ko anchor chahiye. Agar dobaara list scan karni ho toh va_copy use karo, kyunki ek va_list ko rewind nahi kar sakte. Bas itna samajh lo, toh printf ka andar ka jaadu clear ho jaayega!