Skip to main content
A variadic function in C is a function capable of accepting an indefinite number of arguments. It is declared using an ellipsis (...) as the final parameter in its signature. In C standards prior to C23, a variadic function must have at least one named, fixed parameter preceding the ellipsis to provide a reference point for the variable arguments. Starting with the C23 standard, variadic functions can be declared with zero named parameters.

Syntax

The <stdarg.h> Interface

Because the compiler does not enforce type or count checking for the variable portion of the arguments, C provides a set of macros in the <stdarg.h> standard library header to traverse the argument list at runtime.
  • va_list: An opaque state type used to iterate through the variable arguments. Depending on the Application Binary Interface (ABI), this may be a simple pointer or a complex structure managing both hardware register save areas and stack overflow areas (as is common in modern ABIs like x86_64 System V and ARM64).
  • va_start: Initializes the va_list instance.
    • Pre-C23: va_start(va_list ap, last_fixed_arg) requires the name of the last known fixed parameter to initialize the state.
    • C23: va_start(va_list ap, ...) allows initialization with a single argument (ap), accommodating functions with zero named parameters. To maintain backwards compatibility with pre-C23 code, it still accepts a second argument (the last named parameter). Any additional arguments passed to va_start in C23 are evaluated and discarded.
  • va_arg(va_list ap, type): Retrieves the current argument and advances the va_list state to the next argument. The type parameter dictates the byte size and memory alignment to read from the underlying registers or stack.
  • va_end(va_list ap): Cleans up the va_list object. It must be called before the function returns to avoid undefined behavior.
  • va_copy(va_list dest, va_list src): Safely duplicates the state of a va_list, allowing multiple traversals of the argument list.

Implementation Lifecycle

The following demonstrates the structural mechanics of initializing, traversing, and terminating a variadic argument list.

Technical Constraints and Memory Mechanics

Default Argument Promotions When arguments are passed through the ellipsis, C applies default argument promotions. This alters the types expected by va_arg:
  • Integer types smaller than int (e.g., char, short) are promoted to int (or unsigned int).
  • float is promoted to double.
Attempting to extract a pre-promotion type (e.g., va_arg(args, char) or va_arg(args, float)) results in undefined behavior. You must request the promoted type. Argument Resolution The C runtime does not inherently know the number or types of arguments passed to the ellipsis. The function implementation must deduce this information using structural mechanisms, such as:
  1. A fixed parameter specifying the exact count.
  2. A format string containing type specifiers.
  3. A sentinel value (e.g., NULL or -1) terminating the argument list.
Undefined Behavior (UB) Variadic functions bypass standard compiler type-checking, making them highly susceptible to UB. Common triggers include:
  • Invoking va_arg more times than the number of arguments actually passed.
  • Providing a type to va_arg that does not strictly match the promoted type of the passed argument.
  • Failing to invoke va_end before the function returns.
  • Attempting to extract an aggregate type like an array directly. Arrays decay to pointers when passed as arguments to a function, so they must be extracted as their decayed pointer type (e.g., va_arg(args, int*)).
Tired of Poor C Skills? Fix That With Deep Grasping!Learn More