Variadic Functions

Chuan Zhang
The Startup
Published in
4 min readAug 13, 2020

Nov. 30. 2015

A function with variable number of arguments is called a variadic function, and also known as variable argument function [1]. Due to its flexibility in size of the list of input arguments, it is very useful especially in system programming. Its usual use cases include summing of numbers, concatenating strings, and so on. Typical examples include the printf in C programming language, execl and execlp in Unix, _execl and _execlp in Windows, and many others. This post introduces how to declare/define and use such functions.

How to declare a variadic function

Declaration of a variadic function is almost same as an “ordinary” function, except that its formal parameter list ends with an ellipsis. Below is an example of the declaration of a variadic function.

int sum (int x, ...);

In C programming language, at least one named formal parameter must appear before the ellipsis parameter. Whereas in C++, variadic function without any named formal parameter is allowed, although no argument passed to such function is accessible in this case [2].

The comma between the last named parameter and the ellipsis is optional [2], i.e. “, …”, in the above declaration can be replaced by “…”.

How to use a variadic function

Possibly the first example everybody would think of when discussing variadic function is the function printf. Next, we take a look at the source code of this function [3].

int __printf ( const char *format, ... )
{
va_list arg;
int done;
va_start (arg, format);
done = vfprintf (stdout, format, arg);
va_end (arg);
return done;
}

Clearly, the function printf calls vfprintf to implement all the “magics”. However, as the declaration of printf is enough for demonstrating how to define a variadic function, we are not going to dig deeper into the source code of vfprintf. Below the declaration of vfprintf is given.

int vfprintf ( FILE *s, const CHAR_T *format, va_list ap );

In both printf and vfprintf, three macros and one special data type are used to implement variadic functions. They are va_start, va_arg, va_end and va_list. They are defined in the header file stdarg.h (cstdarg for C++). To get a glimpse of its implementation, we take a look at an early version of the header file stdarg.h (see this discussion for details on how to check the source code of the latest stdarg.h).

#define __va_argsiz(t) (((sizeof(t) + sizeof(int) - 1) / sizeof(int)) * sizeof(int))// several lines are skipped #if defined __GNUC__ && __GNUC__ >= 3
typedef __builtin_va_list va_list;
#else
typedef char* va_list;
#endif
// several lines are skipped #ifdef __GNUC__
#define va_start ( ap, pN ) ((ap) = ((va_list) __builtin_next_arg(pN)))
#else
#define va_start ( ap, pN ) ((ap) = ((va_list) (&pN) + __va_argsiz(pN)))
#endif
// several lines are skipped #define va_arg(ap, t) (((ap) = (ap) + __va_argsiz(t)), *((t*) (void*) ((ap) - __va_argsiz(t)))) // several lines are skipped #define va_end(ap) ((void)0)

Now lets summarize these data type and macros.

typedef va_list

A list of variable arguments, a char* variable which will be initialized by the address of the first variable argument.

void va_start(va_list ap, pN);

A macro which takes a va_list variable, ap, and the last named parameter, pN, as its input, and initializes ap by adding the size of pN to its address. Therefore, after invoking va_start, ap points to the address of the first unnamed parameter. (This is why in C programming language, a variadic function must have at least one named parameter.)

T va_arg(va_list ap, T);

A macro retrieving value of the next unnamed parameter. It takes the va_list variable ap and the type of the next unnamed parameter T as input, and return the value of the next unnamed parameter.

It always assumes that an actual argument is passed. So if va_arg() is called when there is no more arguments in ap, the behavior is undefined [4]. The following two ways can solve this problem.

  • when call a variadic function, put a special value, say 0 or NULL, to the end of the list of the input arguments;
  • pass a variable as a named argument which gives the number of arguments being passed.
void va_end(va_list ap);

A macro to cleanup the va_list object initialized by a call to va_start or va_copy [2] by simply setting the pointer to NULL.

Examples

Well, with all the knowledge covered above, we are now ready to use variadic functions. Next, I close the discussion in this note by providing an implementation of the variadic function declared at the beginning of this note.

template<typename T>
T sum (T x, ...)
{
va_list summands;
va_start(summands, x);
T result = 0;
T summand = va_arg(summands, T);
while (summand != 0)
{
result += summand;
summand = va_arg(summands, T);
}
va_end(summands);
return result;
}

PostScript

Actually, similar syntax is also extensively used in templates and macros. Here to demonstrate how to use them, I just give each an example, for more details you can very easily check the references online.

/* variadic_template.cc *//* variadic_macro.cc */
#include <cstdio>
#include <syscall.h>
#include <sys/types.h>
#include <unistd.h>
#define ERR_MSG(frmt, ...) \
fprintf(stderr, "**** Error ****\n"); \
fprintf(stderr, "File: %s, Line: %d\n", __FILE__, __LINE__); \
fprintf(stderr, "Function: %s, PID: %d, TID: %ld\n", \
__func__, getpid(), syscall(SYS_gettid)); \
fprintf(stderr, frmt, __VA_ARGS__); \
fprintf(stderr, "\n");
int main()
{
printf("Testing variadic macro\n");
ERR_MSG("%s %s %s", "Don't worry!",
"This is just a test message!",
"^_^\n");
return 0;
}

References

[1]. Wikipedia: Variadic Function; cplusplus: Variable arguments handling

[2]. C++ Reference: Variadic arguments

[3]. Source Code: printf

[4]. C++ Reference: va_arg

--

--