Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does C have a generic "pointer to a pointer" type?

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".

like image 568
andrew cooke Avatar asked Jan 18 '26 01:01

andrew cooke


1 Answers

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.

like image 60
Steve Jessop Avatar answered Jan 20 '26 17:01

Steve Jessop