Automatically set pointers to NULL after free

144 Views Asked by At

This post shows a way to use void**s to eliminate a category of bugs related to dangling pointers (use after free, double free, etc):

void freep(void **p) {
    if (p) {
        free(*p);
        *p = NULL;
    }
}

I tried it out with the following driver code:

#include <stdlib.h>
#include <string.h>

#define free(x) freep(x)

int main(void) {
    char *s = malloc(5);
    strcpy(s, "hehe");

    char **ss = &s;

    free(s);
    free(ss);
    free(&s);
}

As noted in the post and its comments, it is technically in violation of the C standard - compiled with -Wall -Wextra -pedantic -std=c17, I get warnings regarding passing char* and char** type parameters to a void** type parameters.

My question is, can it be made to not violate the C standard, while still achieving its goal of avoiding dangling pointers by ensuring the user can't forget to set a pointer to NULL after free()-ing it?

Thank you for your time.

4

There are 4 best solutions below

6
ikegami On BEST ANSWER

freep can only set a void * to NULL. You presumably want to set a variety of pointer types (including char *) to NULL.

The only way freep would work is if you did

void *p = s;
freep( &p );
s = p;

Of course, that's ridiculous.

An ordinary function won't do because different pointer types can have different sizes and layouts. Solution:

#define free( p ) do { free( p ); p = NULL; } while ( 0 )

free( s );

Warning: The above evaluates the argument expression twice. It's better to use a name that makes it clear this is a macro rather than overridding free.

#define SAFE_FREE( p ) do { free( p ); p = NULL; } while ( 0 )

SAFE_FREE( s );
1
Scott Hunter On

Something like this might do the job:

#define freep(p) {free(p); p=NULL;}
2
Lundin On

The only generic object pointer type that C supports is void*. This special ability of void* does not apply recursively to void**. Passing a char** to a function expecting a void** is invalid C like the compiler told you with a warning/error.

Therefore the only correct use of the freep function in your question is this:

char *s = malloc(5);
void* vptr = s;
freep(&vptr);

And then we can see why freep is problematic - the bad void** API means that s is still a dangling pointer because only vptr points to null now. Just forgot about this function, it was a bad idea and poorly implemented.

A call for sanity is to forget all about that function and instead write readable standard C:

free(s);
s = NULL;

There exists no reason why you can't write this instead of designing some bad API bloat function as an abstraction layer over 2 lines of readable C code.

A macro would work too, but it's good practice to avoid mysterious macros over well-known standard C.

0
John Bollinger On

As a special case, C specifies that there are automatic conversions between void * and all other object-pointer types. This is not any kind of pattern, but rather a provision specifically for type void *. That is, there is no analogous special case for void **.

You can manually (via typecast) convert among different object pointer types, such as between char ** and void **. Some compilers may even provide such conversions implicitly, which constitutes a language extension. But writing through such a converted pointer violates the strict aliasing rule, producing undefined behavior.*

Overall, no, you cannot make a function of type void f(void **) serve as a general-purpose function for freeing memory and setting pointers to NULL. Alternatives include:

  • use a macro instead:

    #define free(p) do { free(p); p = NULL; } while (0)
    
  • pass an additional argument that conveys the pointer's actual target type:

    enum target_type { VOID, CHAR, INT, DOUBLE };
    void freep(void *pp, enum target_type type) {
        switch (type) {
            case VOID: {
                void **p = (void **) pp;
                free(*p);
                *p = NULL;
                break;
            }
            case CHAR: {
                char **p = (char **) pp;
                free(*p);
                *p = NULL;
                break;
            }
            case INT: {
                int **p = (int **) pp;
                free(*p);
                *p = NULL;
                break;
            }
            case DOUBLE: {
                double **p = (double **) pp;
                free(*p);
                *p = NULL;
                break;
            }
        }
    }
    

    Of course, this requires you to choose in advance which pointer types are supported, so it is not altogether general. But if you want something like this then it might be a good use case for X macros to generate the enum definition and matching function implementation without so much boilerplate.

  • In principle, you could also use a type-generic macro as a front end to multiple type-specific free-and-nullify functions, but I don't see much reason to prefer that over both the preceding options. If you're ok with a macro, then the former is much simpler, and if you're not then you're not.


* One might argue that writing a char * through a void ** -- *voidpp = NULL -- is an allowed case, given that char * and void * are required to have the same representation and alignment requirement, and are generally intended to be interchangeable. But even if one accepts that, it is (another) special case. That does not address your question in its full generality.