0

Currently, I have a very simple function to deallocate array of doubles in my program:

void deallocate(double** array)
{
    free(*array);
}

I would like this function to be variadic in order to take several arrays, and free them one after another. I've never written a variadic function, and as there may exist tricks with pointers I would like to know how to do that.

tshepang
  • 12,111
  • 21
  • 91
  • 136
Vincent
  • 57,703
  • 61
  • 205
  • 388

2 Answers2

3

Don't do this with a variadic function, this concept should be retired. In particular it makes no sense at all for something that is to receive arguments of all the same type, void*.

Just have a simple function, first that receives an array of pointers

void free_arrays(void* a[]) {
  for (size_t i = 0; a[i]; ++i) free(a[i]);
}

Then you can wrap that with a macro like that

#define FREE_ARRAYS(...) free_arrays((void*[]){ __VA_ARGS__, 0 })

This supposes that none of your pointers is already 0, since the processing would stop at that point.

If you'd have a need to have that working even if some of the pointers are 0, you'd have to pass the number of elements as a first parameter to your function. This is a bit tedious but can be determined in the macro, too.

void free_arrays0(size_t n, void* a[]) {
  for (size_t i = 0; i < n; ++i) free(a[i]);
}

#define FREE_ARRAYS0(...)                                    \
  free_arrays(                                               \
              sizeof((void*[]){ __VA_ARGS__})/sizeof(void*), \
              (void*[]){ __VA_ARGS__}                        \
             )
Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • "a bit tedious"? `sizeof((void*[]){ __VA_ARGS__ }) / sizeof(void*)` should work, right? –  Feb 19 '14 at 10:24
  • @hvd, yes, sure, see my edit. Many people would probably consider such code illegible. – Jens Gustedt Feb 19 '14 at 10:29
  • Ah, okay, I'm not one of those, but to improve readability, you could wrap it as `#define LENGTHOF(x) (sizeof(x)/sizeof(*(x))` (already generally useful in other situations), and then do `LENGTHOF((void*[]){ __VA_ARGS__ })` –  Feb 19 '14 at 10:32
  • @JensGustedt _...variadic function, this concept should be retired..._ Care to elaborate? This has been one of the most useful features for me when using straight C. (or were you referring only to this specific application?) – ryyker Feb 20 '14 at 17:34
  • Because of the argument promotion rules and because it is just superflouous to forget everything about the arguments and the reconstruct it dynamically. If all the arguments are of same type you can do similar things as I described in this answer, here. For more see https://gustedt.wordpress.com/2010/08/04/va_arg-functions-and-macros/ and http://gustedt.wordpress.com/2011/07/10/avoid-writing-va_arg-functions/ – Jens Gustedt Feb 20 '14 at 20:10
2

You can do it like this:

void deallocate(double *p, ...)
{
    va_list ap;

    va_start(ap, p);
    do {
        free(p);
        p = va_arg(ap, double *);
    } while (p);
    va_end(ap);
}

Call as deallocate(p1, p2, p3, (double *)NULL). You need the NULL (or some other value) as a sentinel to signal the end of the argument list; none of the other pointers should be NULL or the loop will stop prematurely.

I'm not saying that this is a good idea, though: varargs functions have their use cases, but they're error-prone with pointers because some implicit conversions don't take place (because the compiler doesn't know the type of the arguments beyond the first).

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • You need to actually pass `(double *) NULL` if you read it as a `double *` inside `deallocate`. –  Feb 19 '14 at 10:14
  • @hvd No you don't have to. `void*` is compatible with any pointer and you never dereference it. – this Feb 19 '14 at 10:15
  • @self. Yes, you do. The compiler doesn't know how to implicitly convert `NULL` to any other type when passed through `...`. Easy example of when it breaks: `NULL` is defined as `0`. `int` has 32 bits. `double *` has 64 bits. What do you think will happen? (Also, `void *` is not required to be compatible with other pointer types, it only has to support conversions from and to other pointer types. But implementations where *that* breaks are much rarer.) –  Feb 19 '14 at 10:17
  • @hvd NULL is defined as ( ( void*)0) – this Feb 19 '14 at 10:17
  • @self. How NULL is defined is implementation-defined. `((void *) 0)` is a valid definition. `0` is another valid definition, that does get used on real-world implementations too. –  Feb 19 '14 at 10:18
  • That makes sense. NULL can be defined as 0. Which is bad imo. – this Feb 19 '14 at 10:25
  • @hvd You're right, I was assuming an implicit conversion which won't happen in a varargs context. – Fred Foo Feb 19 '14 at 10:28