Variadic Functions and the Ellipsis (
...
)
In C, the ellipsis (...
) is used in function signatures to indicate that the function can accept an arbitrary number of arguments.
int ft_printf(const char *str, ...)
The arguments passed to the function using this mechanism are called “variable arguments(va)”.
va_list
?At its core, va_list
is a way for C functions to accept a variable number of arguments. It's like a special kind of list that holds all the extra arguments you pass to a function.
There are a few key macros that come into play when using va_list
:
va_start
: Think of this as "starting the list". It initialises the list to point to the first variable argument.va_arg
: This is how you get the next argument from the list.va_end
: This "ends the list" and cleans things up.va_copy
: This is for copying the list.
All these functions are found in the stdarg.h
library.
Let’s create a mini version of printf
that only supports %d
(for integers) and %s
(for strings). It'll showcase va_list
in action.
#include <stdarg.h>
#include <stdio.h>
void mini_printf(const char *format, ...)
{
va_list args; // Declare a va_list variable to manage the variable arguments
// Initialize the va_list 'args' to start at the argument after 'format'
va_start(args, format);
while (*format) // Loop through the format string
{
// If a format specifier is encountered
if (*format == '%')
{
format++;
if (*format == 'd')
{
// Fetch the next argument as an integer and print it
printf("%d", va_arg(args, int));
}
else if (*format == 's')
{
// Fetch the next argument as a string and print it
printf("%s", va_arg(args, char *));
}
}
else
{
// Print regular characters
putchar(*format);
}
format++; // Move to the next character
}
// Cleanup the va_list 'args' after processing
va_end(args);
}
int main(void)
{
mini_printf("Hello %s, number is %d\n", "World", 42);
return (0);
}
Notice how va_start
is used after the fixed argument (format
), and va_arg
retrieves the arguments based on the format specifiers.
va_start
: Initialising the Argument Listva_start
is the starting point. It initialises a va_list
to point to the first
of the variable arguments.
It requires two arguments:
A va_list
variable
The last named argument before the variable arguments
Example:
va_start(args, format);
Here, args
is the va_list
variable, and format
is the last named argument of our function.
When you call a function, the arguments are typically placed onto a stack. va_start
sets up the va_list
to point to the first variable argument on this stack.
va_arg
: Retrieving Argumentsva_arg
fetches the next argument in the list. It moves the pointer forward by the size of the type specified.
It requires two arguments:
The va_list
variable
The desired type of the next argument
Example:
if (*format == 'd')
{
// Fetch the next argument as an integer and print it
printf("%d", va_arg(args, int));
}
This will print the next argument as an integer.
The macro navigates through the stack, fetching arguments based on the size of the specified type. It’s essential to specify the correct type; otherwise, you might retrieve garbage values or cause undefined behavior.
va_end
: Cleaning Upva_end
is the counterpart to va_start
. It cleans up the memory associated with the va_list
.
Without diving too deep into the weeds, here’s a simple explanation:
When you call a function, the arguments you pass in are typically placed onto a “stack” (think of it like a stack of books). With va_list
, we're essentially navigating through this stack to fetch the arguments one by one.
Always have at least one fixed argument: Before the variable arguments, you should have at least one named argument. This is essential for va_start
to work.
The type and number of arguments need clarity: Functions using va_list
don't inherently know the type and number of variable arguments. This is why functions like printf
need format specifiers.
Safety: va_list
can be tricky. If not used correctly, it can lead to undefined behavior. Always ensure that you process the right number and type of arguments.
Also published here.