Variadic Functions in C
Tags: c
Published: 2024-04-27 14:42:12

---
title: Variadic Functions in C
published: true
description: How to declare and implement functions with a variable number of parameters in C.
tags: #c
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-04-23 13:42 +0000
---

## Introduction

C has always used functions that can take a varying number of arguments — _variadic_ functions — `printf()` being the primary example. Originally, C had no way for you to implement your own variadic functions portably.  When [function prototypes](https://en.wikipedia.org/wiki/Function_prototype) were back-ported from C++ to C, it included syntax for declaring variadic functions, for example:

```c
int sum_n( unsigned n, ... );
```

says the function `sum_n` requires one `unsigned` argument followed by zero or more other arguments.

## A Simple Example Using a Count

Here’s a sample implementation of `sum_n()` where we use the required parameter to specify how many arguments follow:

```c
#include <stdarg.h>  /* for va_*() macros */

int sum_n( unsigned n, ... ) {
  va_list args;
  va_start( args, n );
  int sum = 0;
  while ( n-- > 0 )
    sum += va_arg( args, int );
  va_end( args );
  return sum;
}
```

Then you can call it like:

```c
int r = sum_n( 3, 1, 2, 5 );  // r = 8
```

Note that _we_ are using the required parameter to specify how many arguments follow as our own convention here.  The compiler does _not_ infer any meaning from the required parameter.  As we’ll see in a later example, the required parameter can be of any type.

## Variadic Function Recipe

Any variadic function _must_ be of the form:

```c
R f( T1 p1, T2 p2, TN pN, ... ) {
  // ...
  va_list args;
  va_start( args, pN );

  // ... va_arg( args, T ) ...

  va_end( args );
  // ...
}
```

that is you _must_:

1. Declare a local variable of type `va_list`.  (You can name it anything you want, but `args` is conventional.)
2. Call `va_start( args, pN )` where `pN` is the name of the last required parameter.  Note that it can be of _any_ type.
3. To iterate over the values of the variadic arguments, call `va_arg( args, T )` for each argument where `T` is its presumed type.  (The type `T` may be different for each call.)
4. Call `va_end( args )` before returning.

## Another Simple Example Using a Sentinel

Here’s a sample implementation of `str_is_any()` where the required parameter is a string to compare (the “needle”) and the arguments that follow are strings to compare against (the “haystack”).  The arguments are terminated by a `NULL` pointer:

```c
_Bool str_is_any( char const *needle, ... ) {
  va_list args;
  va_start( args, needle );
  _Bool found = false;
  do {
    char const *const hay = va_arg( args, char* );
    if ( hay == NULL )
      break;
    found = strcmp( needle, hay ) == 0;
  } while ( !found );
  va_end( args );
  return found;
}
```

And you can call it like:

```c
if ( str_is_any( type_str, "struct", "union", NULL ) )
```

## Caveats

Variadic arguments have several serious caveats:

1. There is no way to require that any argument be of a specific type nor is there any way to require that all the arguments be of the same type.
2. There is no way to know for certain what the type of any argument actually is.
3. Because there is no type information, _only_ default argument conversions occur (see below).
4. There is no way to know how many arguments were given.  (Attempting to access more arguments than were given results in [undefined behavior](https://dev.to/pauljlucas/undefined-behavior-in-c-and-c-3a20); however, accessing fewer is OK.)
5. Prior to [C23](https://en.wikipedia.org/wiki/C23_(C_standard_revision)), variadic functions had to have at least one required parameter.
6. The `...` must always be last.
7. When iterating over arguments via `va_arg()`, the given type **_must_** match the actual type.  If it doesn’t, the result is undefined behavior.

The default argument conversions are:

+ `char`, `signed char`, `unsigned char`, `short`, and `unsigned short` are promoted to either `int` or `unsigned int` as appropriate.
+ `float` is promoted to `double`.
+ An array is converted to a pointer to its zeroth element.
+ A function name is converted to a pointer to that function.

Hence, the top two problems when implementing a variadic function are:

1. Knowing either the number of arguments or when to stop iterating over them.
2. Knowing their types.

The `sum_n()` implementation “solves” the first problem by using the required parameter to specify how many arguments follow.  However, if you were to do:

```c
int r = sum( 3, 1, 2 );       // said 3, but only 2
```

that is specify that there are 3 arguments that follow but there are fewer, the result would be undefined behavior.

Also, the `sum_n()` implementation can only _assume_ that the provided arguments are of type `int`.  If you were to do:

```c
int r = sum( 3, 1, 2.7, 5 );  // double, not int
```

that is provide a value of type `double` (or any other type) where `int` is expected, the result would be undefined behavior.

The `str_is_any()` function “solves” the first problem by using a sentinel so it doesn’t care how many arguments there are.  However, it still can only _assume_ that the provided arguments are strings _and_ that the last argument is `NULL`.  If either of those are false, the result would be undefined behavior.

The standard `printf()` function “solves” both problems by using the one required argument as the format for what to print: each `%` within the format is a _conversion specifier_ and has a one-to-one correspondence with an argument.  For example, given:

```c
printf( "x=%d, y=%d\n", x, y );
```

the `printf()` implementation scans the format string looking for `%` characters. Upon encountering one, it fetches the next variadic argument’s value via `va_arg()` using the type specified by the character(s) that follow the `%`, e.g., `%d` specifies `int` (and print it in decimal).

However, just as with `sum_n()`, if you either provide fewer arguments than specifiers or the type of a specifier and its associated argument don’t match, the result would be — you guessed it — undefined behavior.

Fortunately, modern compilers have specific knowledge about `printf()` (see “format” [here](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html)), and so can warn when either the number of types of arguments don’t match the format string.  For your own functions, however, you’re generally on your own to get it right.

## Thoughts on Implementing Variadic Functions

Given all their caveats, are variadic functions a good idea?  Not really.  Their use was a hack stemming from C originally not caring about function arguments at all, so functions like `printf()` and `scanf()` took advantage of this.  Even the introduction of `stdarg.h` (and `varargs.h` before that) did only the minimum amount to make implementing variadic functions _portable_, but not _good_.

Should you implement your own variadic functions?  Generally, no.  However, there is one use-case for implementing your own variadic functions.

## Variadic Functions Calling Other Variadic Functions

In a large program that prints many messages, it would be helpful if you could know what line of code printed a given message so you can determine the state of the program at the time the message was printed.

For example, in a program like [cdecl](https://github.com/paul-j-lucas/cdecl), if you get:

```txt
c++decl> explain int &*p
                     ^
13: error: pointer to reference is illegal; did you mean "*&"?
```

you might want to know where in the source code that message was printed from.  In many cases, you can just `grep` for the text of the message, but only if the message text appears literally in the code — which isn’t the case for [this message](https://github.com/paul-j-lucas/cdecl/blob/02a161c7c22628447f5088ae7a8b6cf5d3361746/src/c_ast_check.c#L2170).

I implemented a `debug` option for cdecl that, among other things, prints the source code location whence an error message came:

```txt
c++decl> set debug
c++decl> explain int &*p
                     ^
13: error: [c_ast_check.c:2170] pointer to reference is illegal; did you mean "*&"?
```

The way this is implemented is that there’s an `fl_print_error()` variadic function that’s a wrapper around `fprintf()` that takes additional `file` and `line` arguments whence it was called.  Here’s the (slightly simplified) implementation:

```c
void fl_print_error( char const *file, int line,
                     char const *format, ... ) {
  fprintf( stderr, "error: " );
  if ( opt_cdecl_debug != CDECL_DEBUG_NO )
    fprintf( stderr, "[%s:%d] ", file, line );
  va_list args;
  va_start( args, format );
  vfprintf( stderr, format, args );
  va_end( args );
}
```

and a [macro](https://dev.to/pauljlucas/cc-preprocessor-macros-fh5) that hides the passing of the file and line:

```c
#define print_error(FILE,LINE,FORMAT,...) \
  fl_print_error( __FILE__, __LINE__, (FORMAT), __VA_ARGS__ )
```

If you weren’t aware, `printf()` and `fprintf()` have `vprintf()` and `vfprintf()` counterparts that take a `va_list` parameter:

```c
int vprintf( const char *format, va_list vlist );
int vfprintf( FILE *stream, const char *format, va_list vlist );
```

A `va_list` parameter allows one variadic function to pass its variable arguments to another.

## A Note on C23

As mentioned, as of [C23](https://en.wikipedia.org/wiki/C23_(C_standard_revision)), variadic functions no longer insist on at least one required parameter; that is you can do:

```c
void f( ... ) {      // no required parameter
  va_list args;
  va_start( args );  // no second argument
  // ...
```

This ability was also back-ported from C++.

## Conclusion

Variadic functions in C are basically a hack.  Given their serious caveats, you generally should not implement your own unless it’s a wrapper around another variadic function.

C++ inherited variadic functions from C, warts and all. However, in C++ there are the much better alternatives of function overloading, initializer lists, and variadic templates that can be used to implement functions that accept varying numbers of arguments in a type-safe way — but that’s a [story for another time](https://dev.to/pauljlucas/variadic-functions-in-c-2alg).