[Update 2022 Feb]
If you ask any C expert, <stdarg.h>
is actually to be avoided at all costs. Yes, the key problem is that the API of such a function, imposed on the user, has to “tell” explicitly the length of the arguments list, or use a sentinel for the last argument or have special logic like printf(const char *, ...)
simply peeking into the first argument to decide on the end of operations.
Very clean and simple design is to combine standard variadic macro with callback
Here is the one I prepared earlier.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// ------------------------------------------------------- // (c) dbj at dbj dot org // https://dbj.org/licesense_dbj // // "universal" variadic macro for when // all the arguments are of the same type // // void cb_(size_t size_, T arr[static size_]) // #define vmacro(cb_,T,...) \ cb_( \ sizeof((T[]){__VA_ARGS__}) / sizeof(T) ,\ (T[]){__VA_ARGS__} \ ) |
#include <stdarg.h>
not required. That macro simply prepares two arguments for the callback and then uses the lot. And of course, Godbolt is ready for you.
[The original article 2018 Oct]
Dealing with a variable number of arguments is pretty easy in C++. Alas in real life, you will need to dive into C, from time to time. And possibly meet C/C++ macros with a variable number of arguments, or even worse sounding “Variadic Macro’s“. But why would anybody need any of this?
Like C++ is not “hard enough” already, one might exclaim? Well, standard C++ is not that hard. But to learn anything properly, good examples are priceless. Here is perhaps one good example aka “use case” where one can deploy variadic macros.
(In case you are in a hurry, Godbolt is here)
Sooner or later you realize your C code will be full of malloc()
calls. It is extremely important to get into the habit of free()
ing, anything that should be freed. Just free-free-free, as much as you can. Being “foolishly” involved with standard C too, I have developed my little function to “free in bulk”. Next is the why and the test code.
First here is some heap-allocated data, that requires a lot of free()
calls afterwards.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// note: this is C enum { newlen = 1024, ARRSZ = 9 } ; // global space typedef char * arr_type[ARRSZ] ; // in some function // static array of 9 pointers to the heap space arr_type slave = { calloc( newlen, sizeof(char)), /* 0 */ calloc( newlen, sizeof(char)), calloc( newlen, sizeof(char)), calloc( newlen, sizeof(char)), calloc( newlen, sizeof(char)), calloc( newlen, sizeof(char)), calloc( newlen, sizeof(char)), calloc( newlen, sizeof(char)), calloc( newlen, sizeof(char)) /* 8 */ }; |
In C there are no destructors, so you better be sure you have cleaned up after yourself. A good example is code like above, with a mandatory, long, and tedious sequence of calls to standard function free()
that has to come after it :
1 2 3 4 5 |
// you must call free on everything // you have allocated before free( slave[0] ); free( slave[1] ); free( slave[2] ); free( slave[3] ); free( slave[4] ); free( slave[5] ); free( slave[6] ); free( slave[7] ); free( slave[8] ); |
This is not very nice. And more importantly, that is a bit tiresome. So, I have developed a variadic function with a signature like this:
1 2 3 4 5 |
/* NOTE: must place NULL as the last arg! max allowed args is 255 */ void free_free_set_them_free(void * vp, ...); |
And, I have developed a variadic macro with a normal name to use this useful function:
1 2 3 |
// variadic macro #define FREE(...) \ free_free_set_them_free(__VA_ARGS__) |
That syntax is simple. As is the usage standard inbuilt predefined __VA_ARGS__
that will become arguments list expanded. Now I can ease (a bit), a burden of repeated free calls:
1 2 3 4 5 6 |
FREE( slave[0], slave[1], slave[2], slave[3], slave[4], slave[5], slave[6], slave[7], slave[8], NULL /* do not ever forget NULL as last argument! */ ); |
But. Dear CLANG (aka LVVM) in its standard C reincarnation screams at me now:
1 2 3 4 5 |
warning: passing 'const char *' to parameter of type 'void *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers] free_free_set_them_free(r1,r2,r3, NULL); ^~ note: passing argument to parameter 'vp' here void free_free_set_them_free(void * vp, ...) |
Hmm, casting each argument will be obviously as tedious as typing separate free calls, if not even more tedious:
1 2 3 4 5 6 |
FREE( (void*)slave[0], (void*)slave[1], (void*)slave[2], (void*)slave[3], (void*)slave[4], (void*)slave[5], (void*)slave[6], (void*)slave[7], (void*)slave[8], NULL /* do not ever forget this as last argument! */ ); |
Clearly not acceptable. So, I have tweaked my variadic macro:
1 2 3 4 |
// variadic macro with casting // of a *first* argument #define FREE(...) \ free_free_set_them_free((void *)__VA_ARGS__) |
This macro now expands into:
1 2 3 4 5 6 7 |
// expansion of a FREE variadic macro // with casting the __VA_ARGS__ free_free_set_them_free( (void*)slave[0], slave[1], slave[2], slave[3], slave[4], slave[5], slave[6], slave[7], slave[8], NULL ); |
Just the first argument is cast as requested. And that pacifies the compiler since the remaining args types are not checked before they are used inside the variadic function.
1 2 3 4 5 6 7 |
// using the FREE variadic macro FREE ( slave[0], slave[1], slave[2], slave[3], slave[4], slave[5], slave[6], slave[7], slave[8], NULL /* do not ever forget NULL as the last argument! */ ); |
And (lo and behold) there is no warning and all is dandy (in the land of candy).
But what about this annoying little comment we keep on dragging around?
/* do not ever forget NULL as the last argument!
*/
Standard C, variadic functions implementation is based on what is available in <stdarg.h >. There is no mechanism in there to tell us which is the last argument in standard C variadic functions. In this case, we need to use the concept of the “last argument sentinel”. The last argument is therefore a special value (sentinel) that signals the list of arguments is finished. And since the type of every argument is void *
, NULL is the only logical choice.
Fine. I then do the last and final version of my FREE variadic macro, by simply adding the NULL argument in the call:
1 2 3 4 |
// cast the first argument to (void *) // add the NULL as the last argument, so the caller does not have to #define MULTIFREE(...) \ free_free_set_them_free((void *)__VA_ARGS__, NULL) |
A function is implemented to stop if the argument is NULL. And this is one very good example of why sometimes we need variadic macros and how to use them. The call to MULTIFREE looks now pretty normal, and a good replacement for repeated calls to free().
1 2 3 4 |
MULTIFREE( slave[0], slave[1], slave[2], slave[3], slave[4], slave[5], slave[6], slave[7], slave[8] ); |
No casting and no last argument sentinel necessary.
I am sure you can extrapolate this to your various use cases, with variadic functions in standard C.
Yes, we already said: the code is here. It does not exactly match this post. It is better.