Variadic Functions (va_list)
Create functions that accept a variable number of arguments like printf! Master va_list, va_start, va_arg, va_end, and va_copy to build flexible, reusable functions.
Track Your Progress
Sign in to save your learning progress
What You Will Learn
- ✓Understand what variadic functions are
- ✓Use va_list, va_start, va_arg, and va_end
- ✓Create your own variadic functions
- ✓Handle different argument types safely
- ✓Implement printf-like functions
01Introduction
Have you ever wondered how printf() can accept any number of arguments? Or how scanf() works with different amounts of input? The answer is variadic functions — functions that accept a variable number of arguments.
What You'll Learn
- • How variadic functions work internally
- • The
stdarg.hmacros: va_list, va_start, va_arg, va_end - • Creating your own printf-like functions
- • Common pitfalls and best practices
02What are Variadic Functions?
A variadic function is a function that can accept a variable number of arguments. The function signature uses an ellipsis (...) to indicate that additional arguments may follow.
1// Standard variadic functions you already know:2printf("Hello"); // 1 argument3printf("Number: %d", 42); // 2 arguments4printf("%d + %d = %d", 2, 3, 5); // 4 arguments56// They all use the SAME function!7// Declaration in stdio.h:8int printf(const char *format, ...);9// ^^^ ellipsis = variable argumentsKey Terminology
...Ellipsis — indicates variable arguments in function declarationva_listA type to hold information about variable argumentsva_startInitialize the va_list to start reading argumentsva_argRetrieve the next argument of a specified typeva_endClean up the va_list when done03The stdarg.h Header
To create variadic functions, you need to include <stdarg.h>. This header provides the macros and types needed to access variable arguments.
1#include <stdarg.h>23// Macros provided by stdarg.h:45// 1. va_list - Type for argument pointer6va_list args;78// 2. va_start(va_list, last_fixed_param) - Initialize9va_start(args, last_param);1011// 3. va_arg(va_list, type) - Get next argument12int value = va_arg(args, int);1314// 4. va_end(va_list) - Cleanup15va_end(args);1617// 5. va_copy(dest, src) - Copy va_list (C99)18va_copy(args_copy, args);04Your First Variadic Function
Let's create a simple function that calculates the sum of a variable number of integers:
1#include <stdio.h>2#include <stdarg.h>34// Sum any number of integers5// First parameter 'count' tells us how many numbers follow6int sum(int count, ...) {7 va_list args; // Step 1: Declare va_list8 va_start(args, count); // Step 2: Initialize with last named param9 10 int total = 0;11 for (int i = 0; i < count; i++) {12 // Step 3: Get each argument (specify type!)13 total += va_arg(args, int);14 }15 16 va_end(args); // Step 4: Cleanup17 return total;18}1920int main() {21 printf("Sum of 1, 2, 3: %d\n", sum(3, 1, 2, 3));22 printf("Sum of 10, 20: %d\n", sum(2, 10, 20));23 printf("Sum of 5 numbers: %d\n", sum(5, 1, 2, 3, 4, 5));24 25 return 0;26}Expected Output:
Sum of 10, 20: 30
Sum of 5 numbers: 15
Critical Rule
You MUST have at least one fixed (named) parameter before the ellipsis. The function needs some way to know how many variable arguments to expect!
05How It Works Internally
Understanding the internals helps you use variadic functions correctly and debug issues:
va_start(args, last_param)
- • Points
argsto the first variable argument - • Uses
last_paramto calculate the starting address - • Must be called before any va_arg
va_arg(args, type)
- • Returns the current argument cast to
type - • Advances the pointer to the next argument
- • YOU must specify the correct type!
va_end(args)
- • Cleans up resources used by va_list
- • Required for portability
- • Always call when done!
va_copy(dest, src) (C99)
- • Creates a copy of a va_list
- • Useful for iterating twice
- • Must call va_end on copy too!
1// Visual representation of stack layout:2//3// Stack (high address)4// ┌──────────────────┐5// │ argument 5 │ ← va_arg reads here (5th call)6// ├──────────────────┤7// │ argument 4 │ ← va_arg reads here (4th call)8// ├──────────────────┤9// │ argument 3 │ ← va_arg reads here (3rd call)10// ├──────────────────┤11// │ argument 2 │ ← va_arg reads here (2nd call)12// ├──────────────────┤13// │ argument 1 │ ← va_start points here14// ├──────────────────┤15// │ count (last │ ← last named parameter16// │ named param) │17// ├──────────────────┤18// │ return address │19// └──────────────────┘20// Stack (low address)06Working with Different Types
One of the trickiest parts of variadic functions is handling different argument types. Since C has no runtime type information, you must know or communicate the types somehow.
1#include <stdio.h>2#include <stdarg.h>34// Custom print function with format string5void my_print(const char *format, ...) {6 va_list args;7 va_start(args, format);8 9 for (const char *p = format; *p != '\0'; p++) {10 if (*p != '%') {11 putchar(*p);12 continue;13 }14 15 // Handle format specifier16 p++; // Move past '%'17 switch (*p) {18 case 'd': // Integer19 printf("%d", va_arg(args, int));20 break;21 case 'f': // Double (float promoted to double)22 printf("%f", va_arg(args, double));23 break;24 case 's': // String25 printf("%s", va_arg(args, char*));26 break;27 case 'c': // Character (promoted to int)28 putchar(va_arg(args, int));29 break;30 case '%': // Literal %31 putchar('%');32 break;33 default:34 putchar('%');35 putchar(*p);36 }37 }38 39 va_end(args);40}4142int main() {43 my_print("Hello, %s! You are %d years old.\n", "Alice", 25);44 my_print("Pi is approximately %f\n", 3.14159);45 my_print("Grade: %c\n", 'A');46 47 return 0;48}Default Argument Promotions
When passed to variadic functions, small types are automatically promoted:
- •
char→int - •
short→int - •
float→double
Always use int to retrieve char/short, and double for float!
07Sentinel-Terminated Lists
Instead of passing a count, you can use a special sentinel value to mark the end of the arguments. This is common with string lists and pointer arrays.
1#include <stdio.h>2#include <stdarg.h>34// Concatenate strings until NULL is encountered5void print_all(const char *first, ...) {6 va_list args;7 va_start(args, first);8 9 // Print first argument10 const char *str = first;11 12 while (str != NULL) {13 printf("%s ", str);14 str = va_arg(args, const char*); // Get next string15 }16 printf("\n");17 18 va_end(args);19}2021int main() {22 // NULL terminates the list23 print_all("Hello", "World", "from", "C!", NULL);24 print_all("One", "Two", NULL);25 print_all("Single", NULL);26 27 return 0;28}Expected Output:
One Two
Single
08va_copy for Multiple Iterations
Sometimes you need to iterate through the arguments twice — for example, to calculate length first, then process. Use va_copy (C99) for this:
1#include <stdio.h>2#include <stdarg.h>3#include <stdlib.h>45// Find the max value and sum6void analyze_numbers(int count, ...) {7 va_list args, args_copy;8 va_start(args, count);9 va_copy(args_copy, args); // Create a copy10 11 // First pass: find maximum12 int max = va_arg(args, int);13 for (int i = 1; i < count; i++) {14 int val = va_arg(args, int);15 if (val > max) max = val;16 }17 18 // Second pass (using copy): calculate sum19 int sum = 0;20 for (int i = 0; i < count; i++) {21 sum += va_arg(args_copy, int);22 }23 24 printf("Max: %d, Sum: %d, Average: %.2f\n", 25 max, sum, (double)sum / count);26 27 va_end(args);28 va_end(args_copy); // Must end the copy too!29}3031int main() {32 analyze_numbers(5, 10, 25, 5, 30, 15);33 analyze_numbers(3, 100, 200, 300);34 35 return 0;36}Expected Output:
Max: 300, Sum: 600, Average: 200.00
!Common Pitfalls & Mistakes
1. Wrong Type in va_arg
// WRONG: float is promoted to double!float f = va_arg(args, float); // Undefined behavior!// CORRECT:double d = va_arg(args, double);float f = (float)d; // Cast if needed2. Reading Too Many Arguments
// If caller passes 3 args but you read 5:int sum(int count, ...) { va_list args; va_start(args, count); // If count is wrong, this reads garbage! for (int i = 0; i < count; i++) { total += va_arg(args, int); // May read garbage } va_end(args);}3. Forgetting va_end
void bad_function(int count, ...) { va_list args; va_start(args, count); // ... do stuff ... // OOPS! Forgot va_end(args); // May cause memory leaks or crashes on some platforms}4. Passing NULL for Integers
// WRONG: NULL might not be integer 0 on all platforms!sum(3, 1, NULL, 3); // Undefined behavior// CORRECT:sum(3, 1, 0, 3); // Use actual integer 009Best Practices
Use Format Strings or Counts
Always have a way to know the number and types of arguments. Use format strings (like printf) or an explicit count parameter.
Document Expected Types
Clearly document what types your function expects. Unlike regular functions, the compiler cannot type-check variadic arguments.
Consider Type-Safe Alternatives
For new code, consider using arrays, structs, or C11 _Genericfor type-safe variable argument handling.
Use GCC Format Checking
GCC can check printf-like format strings at compile time:
__attribute__((format(printf, 1, 2)))10Summary
Key Takeaways:
- ✓Variadic functions accept a variable number of arguments using
... - ✓Include
<stdarg.h>for va_list, va_start, va_arg, va_end - ✓va_start initializes, va_arg retrieves, va_end cleans up
- ✓Small types are promoted: char/short → int, float → double
- ✓Use counts, format strings, or sentinel values to know argument boundaries
- ✓Use
va_copy(C99) for multiple iterations over arguments
Test Your Knowledge
Related Tutorials
Functions in C
Organize code into reusable blocks! Functions let you write code once and use it many times. Learn to create, call, and pass data to functions.
Memory Debugging Tools
Find memory bugs with professional tools! Learn Valgrind, AddressSanitizer (ASan), and how to detect memory leaks, buffer overflows, and use-after-free errors.
Pointers in C
The most powerful feature in C! Pointers store memory addresses. Learn what they are, why they matter, and how to use them safely.
Have Feedback?
Found something missing or have ideas to improve this tutorial? Let us know on GitHub!