Search code examples
cpointersstrict-aliasing

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.


Solution

  • 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))