Functions with a Variable Number of Arguments in C

Variable Number of Arguments

Having reached this point, we have seen how to declare and define functions that accept (or not) a certain number of input parameters.

However, the more attentive reader will not have missed that there are functions, such as the library function printf, which can be invoked with a different number of arguments depending on the case.

Consider the following code snippet:

// We invoke the printf function with only one argument
printf("Hello\n");

// We invoke the printf function with two arguments
printf("The result is %d\n", 42);

// We invoke the printf function with three arguments
printf("The result is %d and the remainder is %d\n", 42, 1);

In the example above we invoked the printf function first with only one argument, then with two and finally with three arguments! Which would suggest that the printf function is almost an exception to the rule.

Actually, this is not the case. In fact, in C it is possible to define functions that accept a variable number of arguments through the features made available by the standard library. Let's see how to do it.

Function that accepts a variable number of arguments

First of all, to be able to define a function that accepts a variable number of arguments, we must use a special operator, ..., called ellipsis.

This operator must be positioned at the end of the list of formal parameters of the function. For example, the following function declaration is correct:

void function(int a, int b, ...);

In this case, the function function accepts two parameters of type int and a variable number of arguments of any type.

However, it is not possible to define a function that accepts a variable number of arguments without specifying at least one formal parameter. In fact, the following function declaration is not correct:

// Error:
// at least one formal parameter must be specified
void function(...);

Furthermore, the ellipsis cannot be used in other points of the formal parameter list. For example, the following function declarations are not correct:

// Error:
// The ellipsis cannot be placed at the beginning of the formal parameter list
void function(..., int a);

// Error:
// The ellipsis cannot be placed in the middle of the formal parameter list
void function(int a, ..., int b);
Definition

Ellipsis Operator

The ellipsis operator ... is a special operator in C that allows defining functions with a variable number of arguments. It must be positioned at the end of the formal parameter list and must be preceded by at least one formal parameter. It cannot be used in other points of the formal parameter list.

The correct syntax to define a function with a variable number of arguments is as follows:

return_type function(type1 a, type2 b, ...);

Once we have defined a function with a variable number of arguments, we can invoke it normally while paying attention to respect some conditions:

  1. The arguments corresponding to the explicit formal parameters must be provided in order and with the correct type.
  2. The arguments corresponding to the variable arguments can be provided in any order and with any type, but must be provided in sufficient number for the function to work correctly.

For example, suppose we want to declare a sum function that performs the sum of a certain number of integers. The declaration of the function could be as follows:

int sum(int n, ...);

In this case, n represents the number of arguments we want to sum. The sum function will therefore accept a variable number of arguments of type int.

At this point, we can invoke the sum function in this way:

int result = sum(3, 1, 2, 3);

With the first argument we indicate the number of arguments we want to sum, 3, and with the next three arguments we indicate the numbers to sum, 1, 2 and 3.

Now that we have seen how to declare and invoke a function with a variable number of arguments, let's see how we can implement it.

Library stdarg.h

To implement a function with a variable number of arguments, we must use some macros (we will see macros later) defined in the header file stdarg.h. This header file is part of the standard library of the C language and provides the necessary functionalities to handle variable arguments.

The main macros defined in stdarg.h are:

  • va_start: initializes an object of type va_list to access the variable arguments.
  • va_arg: retrieves the value of the next variable argument of a specified type.
  • va_end: terminates access to the variable arguments.

Let's see how to use these macros to implement the sum function we declared earlier.

#include <stdio.h>
#include <stdarg.h>

int sum(int n, ...) {
    int result = 0;

    // Initializes an object of type va_list
    va_list args;
    va_start(args, n);

    // Sums the variable arguments
    for (int i = 0; i < n; i++) {
        result += va_arg(args, int);
    }

    // Terminates access to the variable arguments
    va_end(args);

    return result;
}

int main() {
    int result = sum(3, 1, 2, 3);
    printf("The sum is: %d\n", result);
    return 0;
}

As can be seen from the example, the implementation of the sum function consists of several steps:

  1. We declare a variable of type va_list called args to access the variable arguments. The type va_list is a so-called opaque type, which means that it does not matter to know exactly how it is made, but only how to use it. In fact, all implementations of the C language can implement va_list in a different way.
  2. We initialize args with the macro va_start, passing as the second argument the number of formal arguments that precedes the ellipsis. In this case, the number of formal arguments is n. Now it is clear why we cannot define a function with a variable number of arguments without specifying at least one formal parameter: we would not know where to start counting the variable arguments!
  3. We retrieve the value of the next variable argument with the macro va_arg, specifying the type of the argument we want to retrieve. In this case, we want to sum only arguments of type int, so we pass int as the second argument to va_arg.
  4. Finally, we close access to the variable arguments with the macro va_end.

There are other macros defined in stdarg.h, but the ones we have seen are the most common and the ones we need to implement a function with a variable number of arguments.

Recapping:

Definition

Macros to Handle Variable Arguments

The main macros defined in stdarg.h to handle variable arguments are:

  • va_start: initializes an object of type va_list to access the variable arguments:

    va_list args;
    va_start(args, number_arguments);
    
  • va_arg: retrieves the value of the next variable argument of a specified type:

    type argument = va_arg(args, type);
    
  • va_end: terminates access to the variable arguments:

    va_end(args);
    

In Summary

In this lesson we introduced a very powerful mechanism provided by the C language to define functions with a variable number of arguments.

In particular we saw that:

  • To define a function with a variable number of arguments, we must use the ellipsis operator ... at the end of the formal parameter list.
  • In addition to the ellipsis operator, we must declare at least one formal parameter.
  • To implement a function with a variable number of arguments, we must use the macros defined in the header file stdarg.h, in particular va_start, va_arg and va_end.
  • The macro va_start initializes an object of type va_list to access the variable arguments.
  • The macro va_arg retrieves the value of the next variable argument of a specified type.
  • The macro va_end terminates access to the variable arguments.
  • The macros va_start, va_arg and va_end must be used in this order to guarantee correct access to the variable arguments.