Skip to main content

Introduction to Variadic Functions

Variadic functions accept a variable number of arguments. Common examples include printf(), scanf(), and sum(). They provide flexibility when you don’t know in advance how many arguments will be passed.
Think of printf(): you can call it with one format specifier or many:
printf("%d", x);              // 2 arguments
printf("%d %s %f", x, s, f);  // 4 arguments

The stdarg.h Header

Variadic functions rely on macros from <stdarg.h>:
MacroPurpose
va_listType to hold argument list information
va_start(ap, last)Initialize the argument list
va_arg(ap, type)Get the next argument of specified type
va_end(ap)Clean up the argument list

Basic Variadic Function

Sum All Arguments

From 0-sum_them_all.c:
#include "variadic_functions.h"
#include <stdarg.h>
/**
 * sum_them_all - function that returns the sum of all its parameters
 * @n: number of arguments
 * Return: sum of all arguments
 * if n==0, return 0
 */
int sum_them_all(const unsigned int n, ...)
{
    int sum = 0;
    unsigned int i;

    va_list lptr;

    va_start(lptr, n);

    for (i = 0; i < n; i++)
        sum += va_arg(lptr, int);

    va_end(lptr);

    return (sum);
}
Breaking it down:
  1. Function signature: int sum_them_all(const unsigned int n, ...)
    • n is the count of remaining arguments (fixed parameter)
    • ... indicates variable arguments follow
  2. Declare va_list: va_list lptr;
    • Holds information about the variable arguments
  3. Initialize: va_start(lptr, n);
    • First parameter: the va_list variable
    • Second parameter: last fixed parameter before ...
  4. Access arguments: va_arg(lptr, int)
    • Get next argument of type int
    • Automatically advances to next argument
  5. Clean up: va_end(lptr);
    • Required before function returns
Usage:
int result;

result = sum_them_all(0);                    // 0
result = sum_them_all(2, 5, 10);             // 15
result = sum_them_all(4, 1, 2, 3, 4);        // 10
result = sum_them_all(5, 10, 20, 30, 40, 50); // 150

Printing with Separators

From 1-print_numbers.c:
#include "variadic_functions.h"
#include <stdarg.h>
#include <stdio.h>

/**
 * print_numbers - function that prints numbers, followed by a new line
 * @separator: string to be printed between numbers
 * @n: number of integers passed to the function
 * Return: null
 */
void print_numbers(const char *separator, const unsigned int n, ...)
{
    va_list valist;
    unsigned int i;

    va_start(valist, n);
    for (i = 0; i < n; i++)
    {
        printf("%d", va_arg(valist, int));
        if (separator != NULL && i < n - 1)
            printf("%s", separator);
    }
    printf("\n");
    va_end(valist);
}
Key features:
  • Takes a separator string (can be NULL)
  • Prints separator between numbers (but not after the last one)
  • Always prints a newline at the end
Usage:
print_numbers(", ", 4, 1, 2, 3, 4);
// Output: 1, 2, 3, 4

print_numbers(" - ", 3, 10, 20, 30);
// Output: 10 - 20 - 30

print_numbers(NULL, 2, 5, 15);
// Output: 515
Why check i < n - 1?This prevents printing the separator after the last element:
1, 2, 3, 4      ✓ Correct
1, 2, 3, 4,     ✗ Wrong (trailing separator)
From 2-print_strings.c:
#include "variadic_functions.h"
#include <stdio.h>
#include <stdarg.h>

/**
 * print_strings - function that prints strings, followed by a new line
 * @separator: string to be printed between each string
 * @n: number of strings passed to the function
 * Retrun: null
 */
void print_strings(const char *separator, const unsigned int n, ...)
{
    va_list valist;
    unsigned int i;
    char *str;

    va_start(valist, n);

    for (i = 0; i < n; i++)
    {
        str = va_arg(valist, char *);

        if (str == NULL)
            printf("(nil)");
        else
            printf("%s", str);

        if (separator != NULL && i < n - 1)
            printf("%s", separator);

    }
    printf("\n");
    va_end(valist);
}
Important safety check:
if (str == NULL)
    printf("(nil)");
Always check if string pointers are NULL before using them! Attempting to print a NULL string with %s causes undefined behavior.
Usage:
print_strings(", ", 3, "Hello", "World", "!");
// Output: Hello, World, !

print_strings(" | ", 2, "First", NULL);
// Output: First | (nil)

Advanced: Print Any Type

Format Specifier Structure

From variadic_functions.h:
/**
 * struct format_specifier - struct for format specifier
 * @specifier: specifier
 * @func: function
 */
typedef struct format_specifier
{
    char *specifier;
    void (*func)(va_list arg);
} format_specifier_t;
This structure maps format characters to printing functions.

Implementation

From 3-print_all.c:
#include "variadic_functions.h"
#include <stdio.h>
#include <stdarg.h>

void print_char(va_list valist);
void print_int(va_list valist);
void print_float(va_list valist);
void print_string(va_list valist);
void print_all(const char * const format, ...);

/**
 * print_char - prints a character
 * @valist:  list of arguments
 * Return: null
 */
void print_char(va_list valist)
{
    printf("%c", va_arg(valist, int));
}

/**
 * print_int - prints an integer
 * @valist:  list of arguments
 * Return: null
 */
void print_int(va_list valist)
{
    printf("%d", va_arg(valist, int));
}

/**
 * print_float - prints a float
 * @valist: list of arguments
 * Return: null
 */
void print_float(va_list valist)
{
    printf("%f", va_arg(valist, double));
}

/**
 * print_string - prints a string
 * @valist: list of arguments
 * Return: null
 */
void print_string(va_list valist)
{
    char *str;

    str = va_arg(valist, char *);

    if (str == NULL)
    {
        printf("(nil)");
        return;
    }

    printf("%s", str);
}

/**
 * print_all - prints anything, followed by a new line
 * @format: is a list of types of arguments passed to the function
 * Return: null
 */
void print_all(const char * const format, ...)
{
    va_list valist;
    int i = 0, j = 0;
    char *separator = "";
    format_specifier_t format_specifiers[] = {
        {"c", print_char},
        {"i", print_int},
        {"f", print_float},
        {"s", print_string}
    };

    va_start(valist, format);

    while (format && (*(format + i)))
    {
        j = 0;

        while (j < 4 && (*(format + i) != *(format_specifiers[j].specifier)))
            j++;

        if (j < 4)
        {
            printf("%s", separator);
            format_specifiers[j].func(valist);
            separator = ", ";
        }

        i++;
    }

    printf("\n");

    va_end(valist);
}
How it works:
  1. Format specifiers array:
    format_specifier_t format_specifiers[] = {
        {"c", print_char},
        {"i", print_int},
        {"f", print_float},
        {"s", print_string}
    };
    
  2. Loop through format string:
    while (format && (*(format + i)))
    
  3. Find matching specifier:
    while (j < 4 && (*(format + i) != *(format_specifiers[j].specifier)))
        j++;
    
  4. Call appropriate function:
    if (j < 4)
    {
        printf("%s", separator);
        format_specifiers[j].func(valist);
        separator = ", ";
    }
    
Usage:
print_all("csi", 'A', "Hello", 42);
// Output: A, Hello, 42

print_all("sif", "String", 98, 3.14);
// Output: String, 98, 3.140000

print_all("csicsfs", 'H', "ello", 98, ',', " World", 3.14, "!\n");
// Output: H, ello, 98, ,, World, 3.140000, !
Clever separator technique:
char *separator = "";
// ...
printf("%s", separator);
format_specifiers[j].func(valist);
separator = ", ";  // Change after first print
This avoids a leading separator without special case logic!

Type Promotion

Important: Some types are automatically promoted when passed as variadic arguments:
  • charint
  • shortint
  • floatdouble
This is why we use:
va_arg(valist, int)     // for char
va_arg(valist, double)  // for float

Common Patterns

Pattern 1: Count-based

First argument specifies count:
int sum_them_all(const unsigned int n, ...)
{
    // n tells us how many arguments follow
}
Pros: Simple, explicit Cons: Caller must count arguments

Pattern 2: Sentinel-based

Special value marks the end:
int sum_until_zero(...)
{
    va_list args;
    va_start(args, ???);  // Problem: need last fixed param
    
    int num;
    int sum = 0;
    while ((num = va_arg(args, int)) != 0)
        sum += num;
    
    va_end(args);
    return sum;
}
Problem: va_start requires at least one fixed parameter before .... Pure sentinel-based approaches need at least one fixed argument.

Pattern 3: Format string

Format string describes the arguments (like printf):
print_all("csi", 'A', "Hello", 42);
//        ^^^     ^^^ ^^^^^^^ ^^
//        format  arguments match format
Pros: Flexible, self-documenting Cons: No type checking, runtime errors possible

Best Practices

  1. Always call va_end
    va_list args;
    va_start(args, last_param);
    // ... use args ...
    va_end(args);  // Required!
    
  2. Don’t mix va_arg types
    // Wrong: asked for int, passed float
    printf("%d", 3.14);  // Undefined behavior
    
    // Correct: match types
    printf("%f", 3.14);
    
  3. Validate pointers
    char *str = va_arg(args, char *);
    if (str == NULL)
        printf("(nil)");
    else
        printf("%s", str);
    
  4. Document expected arguments
    /**
     * print_all - prints various types
     * @format: string with type specifiers:
     *   'c' - char
     *   'i' - int
     *   'f' - float (passed as double)
     *   's' - string (char *)
     */
    
  5. Provide fixed parameter count or format
    // Good: count provided
    sum_them_all(5, 1, 2, 3, 4, 5);
    
    // Good: format describes args
    print_all("si", "Answer:", 42);
    
    // Bad: no way to know when to stop
    mystery_function(1, 2, 3);  // How many args?
    

Common Pitfalls

Type Mismatch

// Wrong: expecting int, got float
va_arg(args, int);  // but caller passed 3.14

// Correct: expect the right type
va_arg(args, double);  // for float arguments

Too Many va_arg Calls

sum_them_all(2, 5, 10);
// In function:
for (i = 0; i < 5; i++)  // Wrong! n=2, not 5
    sum += va_arg(lptr, int);  // Reads garbage after 2nd arg

Missing va_end

void bad_function(int n, ...)
{
    va_list args;
    va_start(args, n);
    // ... use args ...
    return;  // Bug: forgot va_end!
}

Real-world Examples

Standard Library Functions

// printf family
printf(const char *format, ...);
fprintf(FILE *stream, const char *format, ...);
sprintf(char *str, const char *format, ...);

// scanf family
scanf(const char *format, ...);
fscanf(FILE *stream, const char *format, ...);
sscanf(const char *str, const char *format, ...);

Error Handling

void error_exit(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    
    fprintf(stderr, "Error: ");
    vfprintf(stderr, format, args);  // v-version for va_list
    fprintf(stderr, "\n");
    
    va_end(args);
    exit(1);
}

// Usage:
error_exit("File '%s' not found", filename);
error_exit("Invalid argument: expected %d, got %d", expected, actual);
v-versions of printf: Use vprintf(), vfprintf(), vsprintf() when you already have a va_list and want to pass it to a printf-like function.

Key Takeaways

  • Variadic functions accept variable number of arguments using ...
  • Require <stdarg.h> for va_list, va_start, va_arg, va_end
  • Must have at least one fixed parameter before ...
  • Types are promoted: char/shortint, floatdouble
  • Always call va_end before returning
  • Use count, sentinel, or format string to know when to stop
  • No compile-time type checking - be careful with types!

Function Pointers

Combine with function pointers for flexible APIs

Structures & Typedef

Use structs for format specifier tables

Build docs developers (and LLMs) love