DEV Community

loading...
Cover image for Default parameters in C

Default parameters in C

rdentato profile image Remo Dentato ・3 min read

Variadic functions

Modern languages (like Python or Javascript) have a nice feature: they allow a function to have a variable number of arguments and, also, to define a default value for those arguments that are not specified.
This allows a more concise style of programming where some argument is specified only if it has not the "obvious" value.

C allows functions with a variable number of arguments (called variadic functions) through the use of a set of macros defined in the stdarg.h header: va_start(), va_copy(), va_arg() and va_end() that allow you to scan the list of arguments.

While they are better then nothing, I've never really liked them as they are difficult to use and have several drawbacks:

  • You have to define a way to signal the end of parameters. For example, printf() scans the formatting string to know how many parameters to expect while execl() requires you to pass NULL as last argument.
  • There's no type check on the parameters. This can be useful sometimes but not in general and lead to hard to find bugs.
  • The default value must be specified in the logic of the function itself, not in its definition.

C also offers variadic macros, i.e. macros with a variable number of arguments. Using them there are a couple of ways to avoid using stdarg.h and save our day. I'll present here the cleanest one I use with an example and, below, will show how this is achieved.

Example: Let's define a greet() function that takes two arguments (a name and a message) and prints a greetings message but allow for the second argument to be optional. If it is not specified, a default message is used.

#include <stdio.h>
#include "vrg.h"

char * msg_default = "How are you?";
#define greet(...)   vrg(greet, __VA_ARGS__)
#define greet1(n)    greetX(n, msg_default)
#define greet2(n, m) greetX(n, m)

void greetX(char *name, char *msg) {
  printf("Hello %s. %s\n", name, msg);
}

int main(int argc, char *argv[]) {
  greet("Alice", "How's your day?");
  greet("Bob"); // too lazy to think of a greeting for Bob
}
Enter fullscreen mode Exit fullscreen mode

The code above, will produce the output:

Hello Alice. How's your day?
Hello Bob. How are you?
Enter fullscreen mode Exit fullscreen mode

What we did is:

  • name the function somewhat different (greetX()) and write it as we would normally do, with no notion of being variadic.
  • define greet() as a variadic macro (rather than a function)
  • define a greet1() macro for when the greet() macro is called with only one argument.
  • define a greet2() macro for when the greet() macro is called with two arguments.

It works pretty well and it seems to me much easier to write (and understand) than using stdarg.h. Not to mention that this specific case is impossible to cover with stdarg.h since the behaviour of va_arg() is undefined if the argument does not exist!

There is the limitation that you must have at least one argument but this is true also for when you use stdarg.h. GCC has an extension that would allow handling 0 arguments but I prefer to stay with C99.

Under the hood

The trick is in the vrg.h file included in the code above, where vrg serves as a mnemonic for variadic arguments.
Since I've not put it on github yet, I'll show the content of vrg.h here:

#define vrg_cnt(vrg1,vrg2,vrg3,vrg4,vrg5,vrg6,vrg7,vrg8,vrgN, ...) vrgN
#define vrg_argn(...)  vrg_cnt(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define vrg_cat0(x,y)  x ## y
#define vrg_cat(x,y)   vrg_cat0(x,y)

#define vrg(vrg_f,...) vrg_cat(vrg_f, vrg_argn(__VA_ARGS__))(__VA_ARGS__)
Enter fullscreen mode Exit fullscreen mode

The first two macros count the number of arguments. It's limited to no more than 8 arguments because if I ever was writing a function with more than few arguments (and 8 is a lot of arguments), I surely had a big problem in how I structured the function. Simply say no to functions with too many arguments!

The other two macros are the standard way to create an identifier piecewise.

The last one puts all together and calls a different function (or macro) depending on the number of arguments.

Note that this macro is safe as each argument is evaluated only once.

Discussion (2)

pic
Editor guide
Collapse
seanlumly profile image
Sean Lumly

Neat!

I like the argument counting. It is quite a clever use of the variadic macro, effectively "pushing" the argument count value into range with the number of arguments. I've not seen this before, nor considered it.

I was never a fan of variadic functions either, and this gets me thinking of other creative ways to mimic the behaviour.

Collapse
dynamicsquid profile image
DynamicSquid

I love how C doesn't have any "modern" features, but C devs are like screw it, we'll figure out a way ourselves