Exclusive to C
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 likely 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)
Example
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 involved with standard C too, I have developed my little function to “free in bulk”. Next is the “why” part, 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 real C seen in the wild enum { newlen = 1, 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 + 0, sizeof(char)), /* 0 */ calloc( newlen + 1, sizeof(char)), calloc( newlen + 2, sizeof(char)), calloc( newlen + 3, sizeof(char)), calloc( newlen + 4, sizeof(char)), calloc( newlen + 5, sizeof(char)), calloc( newlen + 6, sizeof(char)), calloc( newlen + 7, sizeof(char)), calloc( newlen + 8, sizeof(char)) /* 9 */ }; |
In C there are no destructors, so you better be sure you have cleaned that up before you have left. A good example is code like the above, with a mandatory, long, and tedious sequence of calls to the 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 it is human error-prone. 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, ...); |
Please do not miss that note above. And, I have developed a variadic macro with a normal name to use this useful function:
1 2 3 4 |
// variadic macro // deliberately unfinished, read on #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 the arguments list expanded. Now I can ease (a bit), the 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 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! */ ); |
Not acceptable. So, I have tweaked my variadic macro:
1 2 3 4 5 |
// variadic macro with casting // of a *first* argument to (void *) // no, not finished yet #define FREE(...) \ free_free_set_them_free((void *)__VA_ARGS__) |
First argument? 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 first (void *) cast pacifies the compiler since the remaining args types are not checked before they are used inside the variadic function.
I bet that comes as a surprise.
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 macro candy).
But what about this annoying little comment we keep on dragging?
/* 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 5 |
// cast the first argument to (void *) // add the NULL as the last argument, so the caller does not have to // Yes, this is the final version #define MULTIFREE(...) \ free_free_set_them_free((void *)__VA_ARGS__, NULL) |
A function free_free_set_them_free
is implemented to stop if the argument is NULL. 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 various variadic functions in standard C.
But the better code is here. It does not exactly match this post. It is better.
Warning. That godbolt example contains good C. Make sure to visit, ask questions and use for your use cases.
Wait, there is more!
Let us start with the C function signature, which has an array as an argument.
1 2 3 4 |
// required function signature pseudo code // T means any Type // yes this is legal C void function ( size_t size_, T array_[ static size_] ) |
This might be tedious to call. Try.
Should we do it just with C va_arg and friends? If you ask any C expert, <stdarg.h>
is actually to be avoided. The key problem is that the stdarg.h
APIs are imposing (on the user) the requirement to “tell” explicitly the length of the arguments list, use a sentinel for the last argument or have special logic like printf(const char *, ...)
;
printf is “simply” peeking into the first argument to decide on the number of arguments required.
However, using variadic macros in C, you do not need to use stdarg.h (always). How? Again: Combine standard variadic macro with a function call
It is a macro that expands its arguments to a function call. Calling a function with a special function signature. Ok, how do we apply that here? fiddling with “va macros” from the <stdarg.h>
. In the native array, all elements are of the same type.)
And through the magic of variadic macro, we make the whole lot, kind of comfortable. Here is the one I prepared earlier.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// ------------------------------------------------------- // (c) dbj at dbj dot org // CC BY SA 4.0 // https://dbj.org/licesense_dbj // // variadic macro expanding a call to // void function ( size_t size_, T array_[ static size_] ) // #define VARICALL(cb_,T,...) \ cb_( \ sizeof((T[]){__VA_ARGS__}) / sizeof(T) ,\ (T[]){__VA_ARGS__} \ ) |
VARICALL
is deliberately not short and a bit annoying name. So you remember it is a macro. If that is all confusing here is my explanation. This is standard C.
C++ can not do compound literals.
Standard C compound literal is used to declare arrays inline. Thus doing this (only inside some function please) :
1 |
VARICALL(funcint,int,1,2,3); |
Will expand into:
1 2 3 4 5 6 7 |
funcint( // sizeof array declared inline using the compound array literal sizeof((int[]){1,2,3}) / sizeof(int), // second arg is array itself, again using the compound literal // on expanded macro variadic paremeters (int[]){1,2,3} ) ; |
Where that funcint
is declared as above:
1 |
void funcint(size_t size_ , int p[static size_]) ; |
Yes, that is legal standard C, array argument declaration, requiring a native array of at least size_
elements. And that is the usage with the help of a macro:
1 2 |
// call the funcint with array of three int's VARICALL(funcint,int,1,2,3); |
That is all you need. #include <stdarg.h>
not required. For a reasonably large number of use cases, I think that is a very useful little idiom.
And of course, do not forget that Godbolt for something completely different.
Or … just take it from here. (this feels strange, have not posted functioning code for several yeas now. Or is it more?)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
// // (c) dbj@dbj.org , CC BY SA 4.0 // -std=c2x // #include <assert.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> enum { max_args = 255 } ; // never without max args typedef void myvararg_callback ( void * ) ; static myvararg_callback my_print_ ; /* ------------------------------------------------------------------------- */ /* solving the multi-alloc-multi-free from the article https://dbj.org/c-macros-with-variable-number-of-arguments/ a bit more realistic example (for Arena lovers) */ #define ARENA_SIZE 0xFF #define ARENA_BLOCK_SIZE 0xFF static unsigned char * arena[ARENA_SIZE] = {0}; myvararg_callback make_arena; myvararg_callback free_arena; myvararg_callback print_arena; // arg is a pointer to arena index void make_arena ( void * arg ) { assert(arg); int arena_index = *((int *)arg); assert(arena_index >= 0 && arena_index < ARENA_SIZE ); // assert( arena[arena_index] == 0 ); arena[arena_index] = calloc(ARENA_BLOCK_SIZE, sizeof(unsigned char)); } // arg is a pointer to arena index void free_arena ( void * arg ) { assert(arg); int arena_index = *((int *)arg); assert(arena_index >= 0 && arena_index < ARENA_SIZE ); // assert( arena[arena_index] != 0 ); free( arena[arena_index]); arena[arena_index] = 0; } // arg is a pointer to arena index void print_arena ( void * arg ) { assert(arg); int arena_index = *((int *)arg); assert(arena_index >= 0 && arena_index < ARENA_SIZE ); assert( arena[arena_index] != 0 ); printf("\narena[%d] = %p", arena_index, arena[arena_index] ); } /* ------------------------------------------------------------------------- */ static void my_print_ ( void * p ) { assert(p); static unsigned cnt_ = 0; printf("Argument %d %8s[%p] \"%s\"\n", ++cnt_, " ", p,(char *)p); } // // this works only if second argument is void * // AND if last argument in NULL // can do max max_args arguments static void myvararg( myvararg_callback callback_fptr , void *ptr, ...) { assert(callback_fptr); unsigned cnt_ = 0; va_list va; va_start(va, ptr); for (void * p = ptr; p != NULL && cnt_ < max_args ; p = va_arg(va, void *)) { callback_fptr(p); } assert(max_args > cnt_ ) ; va_end(va); } // macro to call the above without a fear to forget the last 0 arg #define MYVARARG( worker, ...) \ myvararg(worker, (void *)__VA_ARGS__, NULL) int main() { // last argument must be NULL MYVARARG( my_print_, "A", "B", "C"); // yes this can be done more cleverly and more obfuscated unsigned int zero = 0, one = 1, two = 2; // the requirement is to manage different blocks // of the arena independently MYVARARG(make_arena, &zero,&one,&two); MYVARARG(print_arena,&one,&zero,&two); MYVARARG(free_arena, &two,&one,&zero); return 42; } |