5.1.29 · D1C Programming

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

2,354 words11 min readBack to topic

Before you can read the parent note, you must own every word it leans on. This page builds them from zero — plain words first, then the picture, then why the topic needs it. Nothing here assumes you have written C before.


0. What is a "function argument" really?

The parent note keeps saying "named parameter" and "the extra arguments". The whole trick is that some arguments have cups (parameters) waiting for them, and some do not. Hold that image — everything below is about the argument that arrives with no cup.

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

Look at the figure. The named parameter n (this is our last) gets a labelled cup. The three extra values 10, 20, 30 land on the counter with no label — that unlabelled region is the entire problem the <stdarg.h> toolkit solves.


1. The stack — where the extra values wait

Read more in Calling Conventions and the Stack — but for this page you only need: arguments live in a line the macros can walk.

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

The red cursor in the figure is a pointer. That's the next symbol we must define.


2. A pointer — the "cursor" the parent keeps mentioning

Picture: &x points a finger at a box. *p reaches into the box p points at and pulls out its contents.


3. sizeof — how far to step

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

In the figure, each box is a different width because each type has a different sizeof. This is why va_arg needs you to name the type — the type tells it how wide the current box is.


4. Types and the promise you make


5. Default argument promotions — the sneaky size change

This single rule is the source of the parent's biggest "Mistake" — you now understand why it happens.


6. Macros — why the toolkit is spelled in #define


7. The ellipsis ..., the opaque va_list, and va_copy


Prerequisite map

Argument vs Parameter

The Stack: args in a line

Pointer: address plus deref

sizeof: how far to step

Types: how to read the bytes

Default Argument Promotions

Macros: can take a type

Registers plus stack: va_list abstracts both

Variadic Functions

Every arrow says "you need the box on the left before the box on the right makes sense." All roads end at Variadic Functions — the parent topic. Related deeper reading: C Standard Library, Function Pointers in C, Format String Vulnerabilities.


Equipment checklist

Test yourself — cover the right side.

An argument is...
the actual value passed at the call site (the water), while a parameter is the named cup waiting for it.
In va_start(ap, last), last refers to...
whatever you named the last named parameter before the ... (e.g. n in int sum(int n, ...)).
ap in the code samples is...
the cursor variable you declared with va_list ap; — "ap" just abbreviates argument pointer; the name is your choice.
The stack gives us...
a predictable line of memory holding (some of) the arguments, which lets a cursor step through them in order.
Are all variadic arguments always on the stack?
No — many ABIs pass the first few in CPU registers; va_list abstracts over register area plus stack so you never care which.
A pointer holds...
a memory address (a house number), not the value itself.
&x means...
address-of: the location of x.
*p means...
dereference: read the value stored at the address p holds.
sizeof(T) tells the cursor...
how many bytes wide the current value is — though real ABIs also add alignment padding the macros must skip.
Why can't you hand-roll ap += sizeof(T)?
alignment and padding rules differ per platform; a naive increment lands in padding and misreads. Let the macros handle it.
A type in va_arg is the promise that...
this box is that many bytes and should be interpreted this way.
Default argument promotions turn char/short into...
int, and float into double.
So you must never write va_arg(ap, float) but instead...
va_arg(ap, double).
va_start, va_arg, va_end, va_copy are macros because...
only macros can accept a type name as an argument and modify ap in place.
... (ellipsis) means...
zero or more additional arguments of unknown type follow, un-type-checked.
va_list is opaque, so you may only...
touch it through the macros — never inspect its size or =-copy it (use va_copy).
va_copy(dst, src) exists because...
a va_list may be single-use and non-copyable with =; va_copy gives an independent cursor at the same spot so you can walk the list twice.