thecodex.expert · The Codex Family of Knowledge
C

Variadic Functions

printf has no built-in way to know how many arguments you actually passed — it trusts the format string entirely, which is exactly why a mismatched format specifier is a real, dangerous bug.

C17 / C23 stdarg.h Last verified:
Canonical Definition

A variadic function's last declared parameter is ..., accepting any number of additional arguments after the fixed ones. Inside the function, va_start begins accessing the variable arguments, va_arg retrieves each one in order (requiring the caller to specify its type explicitly), and va_end cleans up. Critically, C has no automatic mechanism telling the function how many arguments were actually passed or what their types are — printf works around this by parsing its own format string, meaning a mismatched format specifier (like %d for a value that's actually a double) is undefined behavior, not a caught error.

Writing a variadic function with stdarg.h

Since C doesn't automatically track argument count, this function needs its own way to know when to stop — here, a sentinel value (-1) marks the end of the list, one of a few common conventions for this problem.

Cvariadic_sum.c
#include <stdarg.h>

int sum(int count, ...) {   // count tells us how many follow — one common convention
    va_list args;
    va_start(args, count);   // start reading arguments AFTER count

    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);   // must specify the TYPE explicitly each time
    }

    va_end(args);   // required cleanup
    return total;
}

printf("%d\n", sum(3, 10, 20, 30));   // 60 — count=3 tells sum() how many ints follow

Why printf trusts the format string, dangerously

printf has no way to verify the argument's actual type matches %d — if you pass a double where %d expects an int, the behavior is undefined, not a caught error; this is exactly why compilers like GCC add special format-string checking specifically for printf-family functions, since the language itself provides none.

Cprintf_danger.c
double pi = 3.14159;
printf("%d\n", pi);   // UNDEFINED BEHAVIOR: %d expects int, pi is double — likely garbage output

// gcc -Wall specifically flags this exact mistake via -Wformat, worth always enabling

The NULL/sentinel-terminated alternative

Rather than passing an explicit count, some variadic functions use a special terminating value in the argument list itself to know when to stop — the caller must remember to include it, another manual responsibility C doesn't enforce.

Csentinel_pattern.c
#include <stdarg.h>
#include <stdio.h>

void printStrings(const char *first, ...) {
    va_list args;
    va_start(args, first);

    const char *s = first;
    while (s != NULL) {
        printf("%s\n", s);
        s = va_arg(args, const char *);
    }
    va_end(args);
}

printStrings("a", "b", "c", NULL);   // NULL marks the end — easy to forget it

Sources

1
ISO/IEC 9899 (C Standard), "Variable arguments <stdarg.h>," cppreference.com/w/c/variadic.