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
c.core.17— functions.
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
- Reading the wrong type with
va_arg(e.g., reading afloatasfloat
instead of double).
- Not calling
va_end. - Not providing a way for the function to know the count/types.
- Forgetting that
va_startrequires the last *named* parameter (not the first
variadic one).
Undefined Behavior
- Reading an argument with a type incompatible with the promoted actual
argument type. VERIFIED
- Reading more arguments than were passed.
VERIFIED - Using
va_argafterva_endor without a matchingva_start.VERIFIED - Passing a
va_listwhose contents are indeterminate.
Portability
va_list,va_start,va_arg,va_end,va_copyare standard (C99 for
va_copy).
- The exact mechanism (registers vs. stack) is ABI-specific, but the macros
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
- Use variadic functions for logging, formatting, and generic aggregation.
- Prefer the
v*functions to passva_listthrough to standard library
formatting functions.
- Always document how callers signal argument count and types.
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.
Related Concepts
c.stdlib.2— printf family.c.pp.variadic-macro— variadic macros.c.build.calling-convention— how arguments are passed.
References
- ISO/IEC 9899:2018 §7.15 (stdarg.h), §6.7.6.3 (function declarators),
§6.5.2.2 (function calls, default argument promotions).
Verification
- Default argument promotions apply to variadic arguments.
VERIFIED - Wrong-type
va_argread is UB.VERIFIED va_copyis C99.VERIFIED- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Variadic function declaration
- [ ] va_list/va_start/va_arg/va_end
- [ ] va_copy
- [ ] Default argument promotions
- [ ] vprintf-style wrapping
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.func.variadic | 0 | 6 |