C Mastery / Variadic Functions and stdarg.h
Part 1 — The Core Language

Variadic Functions and stdarg.h

This chapter covers functions that accept a variable number of arguments, using the <stdarg.h> macros. Variadic functions are how printf-style APIs work, and they carry specific type and portability rules.

Why This Matters

Variadic functions are the foundation of formatted I/O and logging in C. They are also a rich source of undefined behavior because the compiler cannot type check the variable arguments.

Prerequisites

Core Concept

A variadic function has a fixed, named parameter list followed by ...:

int sum(int count, ...);

The function reads the variable arguments using the <stdarg.h> macros:

#include <stdarg.h>

int sum(int count, ...)
{
    va_list ap;
    va_start(ap, count);   /* initialize; last named param is count */

    int total = 0;
    for (int i = 0; i < count; i++)
        total += va_arg(ap, int);   /* read next int argument */

    va_end(ap);
    return total;
}

The caller must somehow tell the function how many arguments there are and of what type (via a count, a format string, a sentinel, etc.). The function cannot discover this itself.

Syntax

return_type name(fixed_params, ...);

va_list ap;
va_start(ap, last_named_param);
type v = va_arg(ap, type);
va_end(ap);

There is also va_copy(dst, src) (C99) for copying a va_list.

Examples

Sum with a count

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

int sum(int count, ...)
{
    va_list ap;
    va_start(ap, count);
    int total = 0;
    for (int i = 0; i < count; i++)
        total += va_arg(ap, int);
    va_end(ap);
    return total;
}

int main(void)
{
    printf("%d\n", sum(3, 10, 20, 30));
    return 0;
}

Expected output: 60.

printf-style with a format string

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

void log_msg(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    vprintf(fmt, ap);   /* v* functions take a va_list */
    va_end(ap);
}

vprintf (and vfprintf, vsnprintf, etc.) are the va_list-taking versions of the printf family, letting you wrap them.

How It Works

The variable arguments are passed according to the ABI, and va_start/va_arg walk through them using the last named parameter to locate the start. The compiler does not know the types of the variable arguments, so it is the function's responsibility to read them correctly.

Variations

Default argument promotions

When arguments are passed through ..., they undergo the default argument promotions: float becomes double, and small integer types become int (or unsigned int). This means you must read float arguments as double and char/short arguments as int with va_arg. VERIFIED

va_copy

va_copy(dst, src) makes a second va_list that can be traversed independently. Useful when you need to scan arguments twice.

Common Mistakes

instead of double).

variadic one).

Undefined Behavior

argument type. VERIFIED

Portability

va_copy).

hide it.

Under the Hood

On x86-64 System V, the first several integer and floating arguments are passed in registers, with a "register save area" used by va_arg. The va_list is effectively a cursor over that area plus the stack. The compiler and ABI cooperate to make the macros work.

Practical Usage

formatting functions.

Exercises

1. Write a max_int variadic function that takes a count and returns the maximum. 2. Wrap printf in a logging function using vprintf. 3. Demonstrate default argument promotions by passing a float and reading it correctly as double. 4. Write a variadic function that sums double arguments.

Deep Challenge

Implement a tiny snprintf-like formatter that supports %d, %s, and %c using va_arg, handling the argument promotions correctly and always null-terminating the output. Explain each type-read decision.

References

§6.5.2.2 (function calls, default argument promotions).

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.func.variadic06