CVariadic Functions (stdarg.h)

Variadic Functions (stdarg.h)

A variadic function accepts a variable number of arguments — you don't fix the count when you write the function. You've been calling one all along: printf(const char *format, ...) can take one argument or a dozen, depending on how many format specifiers are in the format string. C lets you write your own variadic functions using the macros in <stdarg.h>.

Declaring a variadic function

A variadic function needs at least one fixed, named parameter, and its parameter list ends with an ellipsis ... marking "zero or more additional arguments follow." The fixed parameter conventionally tells the function how many extra arguments to expect, or what format they're in:

C
int sum(int count, ...); /* count tells sum() how many ints follow */
The stdarg.h macros

Macro

Purpose

va_list

A type used to declare a variable that tracks your position in the argument list

va_start(list, last_fixed_param)

Initializes the va_list, positioned right after the last named parameter

va_arg(list, type)

Retrieves the next argument, interpreted as type, and advances the position

va_end(list)

Cleans up the va_list — must be called before the function returns

Worked example: a variadic sum() function

C
#include <stdio.h>
#include <stdarg.h>

int sum(int count, ...) {
    va_list args;
    int total = 0;

    va_start(args, count); /* start reading right after "count" */
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int); /* read the next int, advance */
    }
    va_end(args); /* required cleanup */

    return total;
}

int main(void) {
    printf("%d\n", sum(3, 10, 20, 30));  /* 60 */
    printf("%d\n", sum(5, 1, 2, 3, 4, 5)); /* 15 */
    printf("%d\n", sum(0));                /* 0 -- no extra arguments at all */
    return 0;
}
60
15
0
How the function knows when to stop

Notice that sum relies entirely on the count parameter to know how many arguments follow — there's no way to ask "how many variadic arguments were actually passed?" at runtime. This is exactly why printf counts its format specifiers (%d, %s, and so on) to know how many extra arguments to pull off the list: the format string plays the same role that count plays in sum.

  • A sentinel value can also mark the end, e.g. a NULL-terminated list of char * arguments.

  • Whatever convention you choose, it is entirely your responsibility to pass matching information — the compiler cannot check it for you.

No type safety on the extra arguments
The compiler does not check that the arguments you actually pass match the types `va_arg` will request. If `sum`'s caller passed a `double` where the function calls `va_arg(args, int)`, or passed fewer arguments than `count` claims, the result is **undefined behavior** — there is no bounds checking and no type checking anywhere in this mechanism. Variadic functions trade compile-time safety for flexibility, so use them sparingly and document their expected argument types clearly.
Default argument promotions apply
When you pass an argument to a variadic function's `...` part, it undergoes the same "default argument promotions" as an unprototyped call: `float` is promoted to `double`, and integer types smaller than `int` (like `char` or `short`) are promoted to `int`. That's why you always call `va_arg(args, double)` for a floating-point argument, never `va_arg(args, float)`, even if the caller technically passed a `float`.