Kusalananda Kusalananda - 4 months ago 40
C Question

Calling a free() wrapper: dereferencing type-punned pointer will break strict-aliasing rules

I've tried to read up on the other questions here on SO with similar titles, but they are all a tiny bit too complex for me to be able to apply the solution (or even explanation) to my own issue, which seems to be of a simpler nature.

In my case, I have a wrapper around

free()
which sets the pointer to
NULL
after freeing it:

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


In the project I'm working on, it is called like this:

myfree((void **)&a);


This makes
gcc
(4.2.1 on OpenBSD) emit the warning "dereferencing type-punned pointer will break strict-aliasing rules" if I crank up the optimization level to
-O3
and add
-Wall
(not otherwise).

Calling
myfree()
the following way does not make the compiler emit that warning:

myfree((void *)&a);


And so I wonder if we ought to change the way we call
myfree()
to this instead.

I believe that I'm invoking undefined behaviour with the first way of calling
myfree()
, but I haven't been able to wrap my head around why. Also, on all compilers that I have access to (
clang
and
gcc
), on all systems (OpenBSD, Mac OS X and Linux), this is the only compiler and system that actually gives me that warning (and I know emitting warnings is a nice optional).

Printing the value of the pointer before, inside and after the call to
myfree()
, with both ways of calling it, gives me identical results (but that may not mean anything if it's undefined behaviour):

#include <stdio.h>
#include <stdlib.h>

void myfree(void **ptr)
{
printf("(in myfree) ptr = %p\n", *ptr);
free(*ptr);
*ptr = NULL;
}

int main(void)
{
int *a, *b;

a = malloc(100 * sizeof *a);
b = malloc(100 * sizeof *b);

printf("(before myfree) a = %p\n", (void *)a);
printf("(before myfree) b = %p\n", (void *)b);

myfree((void **)&a); /* line 21 */
myfree((void *)&b);

printf("(after myfree) a = %p\n", (void *)a);
printf("(after myfree) b = %p\n", (void *)b);

return EXIT_SUCCESS;
}


Compiling and running it:

$ cc -O3 -Wall free-test.c
free-test.c: In function 'main':
free-test.c:21: warning: dereferencing type-punned pointer will break strict-aliasing rules

$ ./a.out
(before myfree) a = 0x15f8fcf1d600
(before myfree) b = 0x15f876b27200
(in myfree) ptr = 0x15f8fcf1d600
(in myfree) ptr = 0x15f876b27200
(after myfree) a = 0x0
(after myfree) b = 0x0


I'd like to understand what is wrong with the first call to
myfree()
and I'd like to know if the second call is correct. Thanks.

Answer

Since a is an int* and not a void*, &a cannot be converted to a pointer to a void*. (Suppose void* were wider than a pointer to an integer, something which the C standard allows.) As a result, neither of your alternatives -- myfree((void**)a) and myfree((void*)a) -- is correct. (Casting to void* is not a strict aliasing issue. But it still leads to undefined behaviour.)

A better solution (imho) is to force the user to insert a visible assignment:

void* myfree(void* p) {
    free(p);
    return 0;
}

a = myfree(a);

With clang and gcc, you can use an attribute to indicate that the return value of my_free must be used, so that the compiler will warn you if you forget the assignment. Or you could use a macro:

#define myfree(a) (a = myfree(a))