3

For example, if I wanted to write a "free" that nulled the pointer, I could write something like:

void myfree(void **data) {
    free(*data);
    *data = NULL;
}

however, when I try to write this, I get a compiler warning (from gcc 4.6.2) saying: warning: passing argument 1 of ‘myfree’ from incompatible pointer type [enabled by default] ... note: expected ‘void **’ but argument is of type ‘char **‘ (in this case, I am freeing a char array).

It seems that void* is special cased to avoid this kind of warning, since calloc, free etc. don't trigger such warnings, but void** is not (given the above). Is the only solution an explicit cast, or have I misunderstood something?

[I am revisiting some pain points in a recent project, wondering how they could have been handled better, and so am poking at corner cases, hence the C questions today.]

update given that void* is special cased, I could hack around this using void* and casts inside myfree, but that would be a somewhat irresponsible solution because everyone and their dog are going to pass a pointer to something that looks like free, so I need some kind of compiler warning based on "degree of indirection" for this to be a practical solution. hence the idea of a generic "pointer to a pointer".

andrew cooke
  • 45,717
  • 10
  • 93
  • 143
  • Just cast to `void**` when passing. – Daniel Fischer Jun 08 '12 at 17:57
  • right, but it's ugly + messy, especially when `free` doesn't need it. hence wondered if there was anything better. – andrew cooke Jun 08 '12 at 17:58
  • As an aside, IMO you should not routinely set pointers to null when you free them. There are situations where you do need to, and there are situations where you can't (for example if the pointer itself is `const`). Other people think it's very important to always set to null, but I think they should instead design their code differently, so that they have fewer dangling pointers in the first place. Most (not all) of the time, you free a pointer just before you free the struct that contains it, or just before the pointer itself goes out of scope. So there's no point nulling it: it's gone. – Steve Jessop Jun 08 '12 at 18:27

2 Answers2

5

Technically, the standard allows different object pointer types to have different representations (even different sizes), although char* and void* are required have the same representation. But the following is UB:

int *ip = 0;
free(*(void**)(&ip));

simply because the memory for ip need not be the same size as the memory for a void*, and even if it is the bit pattern for a null pointer of type int* need not be the same as the bit pattern for a null pointer of type void*. If they're different, then of course the compiler has to insert code to convert between them whenever you convert an int* to void* or back.

In practice, implementations don't do that to you (and for example Posix forbids it).

More importantly though, the strict aliasing rules don't allow you to access a char* object using an lvalue of type void*. So in practice, concerns about pointer representation will not break your code, but the optimizer actually might. Basically, if the function call myfree((void**)(&p)) gets inlined, then the compiler might see:

char *p = <something>;
void **data = (void**)(&p);
free(*data);
*data = NULL;
// code that reads p

The optimizer is allowed to note that *data = NULL is setting an object of type void*, whereas the "code that reads p" is reading an object of type char*, which is forbidden from being aliased with that other, void* object over there. So it is allowed to reorder the instructions, eliminate *data = NULL; entirely, or possibly other things I haven't thought of that will ruin your day, but that would speed the code up if you hadn't broken the rules.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • UB? what does that mean (sorry)? – andrew cooke Jun 08 '12 at 18:09
  • Undefined Behavior. It means that the standard doesn't care what happens. Implementations can do anything they like, and often whatever they do is accidental and hence is unpleasant and/or inconsistent. – Steve Jessop Jun 08 '12 at 18:10
  • ok, thanks, i am going to need to think. right now it's not clear to me how some of this is undefined, yet stdlib manages fine with void*. – andrew cooke Jun 08 '12 at 18:11
  • 1
    You can *convert* any object pointer type to `void*`, but that conversion might (is permitted by the standard to) take time and effort, and involve changing the bit-pattern. Hence you can't (aren't allowed to) just read another pointer type as if it was a `void*`. Similarly, you can convert an `int` to `double`, but you can't just read an `int` as if it were a `double`. – Steve Jessop Jun 08 '12 at 18:13
  • The reason for the strict aliasing rules is that allowing for potential aliasing genuinely does slow down programs even when no aliasing actually occurs. So rules which allow the compiler to assume that no aliasing occurs, speed up programs. It is a trade-off, though, since sometimes you want to alias different types but the language doesn't let you. – Steve Jessop Jun 08 '12 at 18:15
-1

You can use the MACRO to do this operation. This will be really great compare to having a function; I hope you know the advantage of using MACRO.

#define FREE_IF_NOT_NULL(x) if (x != NULL) { \
                                            free(x); \
                                            x = NULL; \
                                       }
Viswesn
  • 4,674
  • 2
  • 28
  • 45