4

I want to set all the pointers pointing to a freed memory location to NULL, so that no dangling pointers or double frees are possible. Is this possible in C?

For example, I have the following structures:

struct B {
    int *arr;
    unsigned int len;
};

struct A {
    struct B *b;
};

// Freeing and setting them to NULL:
bool test_safe_free() {
    struct A *a = malloc(sizeof(struct A));
    struct B *b = malloc(sizeof(struct B));
    b->arr = malloc(100 * sizeof(int));
    b->len = 100;
    a->b = b;

    safe_free_A(&a);
    return a == NULL && b == NULL;
}

void safe_free_B(struct B **b_ref) {
    if (*b_ref != NULL) free((*b_ref)->arr);
    (*b_ref)->arr = NULL;
    free(*b_ref);
    *b_ref = NULL;
}

void safe_free_A(struct A **a_ref) {
    // Before freeing A, freeing B:
    if (*a_ref != NULL) safe_free_B(&((*a_ref)->b));
    free(*a_ref);
    *a_ref = NULL;
}

The test_safe_free function returns false, because even though the variable a is set to NULL after freeing, b is still pointing the freed memory, because the pointer is copied (and the copy is set to NULL, while the original remains the same) when a passed into the function.

I couldn't come up with a way, a structure to resolve this, but I'm also not sure whether what I'm trying to do is even possible.

  • FYI: [Don't cast the return value of `malloc`](https://stackoverflow.com/q/605845/119527). – Jonathon Reinhart Nov 26 '19 at 18:38
  • @JonathonReinhart Thanks for the information. – M. Samil Atesoglu Nov 26 '19 at 18:48
  • Even if you could do this, it would be a bad idea. Those pointers magically set to NULL, if used, would cause chaos down the line. Your problem is an excellent argument for garbage collection. – ddyer Nov 26 '19 at 18:54
  • @ddyer what chaos? The standard actually permits a compiler to set any pointers to freed storage to NULL or any other value . If a program tries to use the value of a pointer to freed storage the the program has undefined behaviour – M.M Nov 26 '19 at 21:28
  • @ddyer Didn't think about it that way. I wanted to avoid double frees when I free a struct A that contains another struct B, then free that struct B too somewhere else. For example: If I have a list of struct A's and another list of B's, if some B's of that list are already in some of the A's in the list of A's, and I try to free both lists, I get double free. The only way I could think of to avoid having to track where to free what, and avoid double frees was setting every pointer that points to a freed memory location to NULL. – M. Samil Atesoglu Nov 26 '19 at 21:34
  • @M.M I think you're misreading the standards - the former contents of freed memory can be set to any value. There's no way to find other pointers to the same storage, which was the original question. If there actually are any other pointers to freed memory, they are time bombs waiting to explode. Making them NULL is no help - you'll just fall into whatever undefined behavior ensues when something you "knew" had to be valid is suddenly null instead. Maybe less immediately disastrous, but even harder to debug. – ddyer Nov 27 '19 at 22:25
  • Anyway, the usual way to deal with this problem in C is to manually implement reference counts for dynamically allocated memory, or enforce (by good programming discipline) that there is only one pointer, or for programs with a guaranteed short lifetime, to just ignore the problem and don't free anything. – ddyer Nov 27 '19 at 22:41
  • @ddyer the compiler may know where some other pointers to the same storage are . It would make it easier to debug to set those to some known value that will show up in debugging. – M.M Nov 27 '19 at 23:18
  • @m.m. if the compiler knew where those pointers were, it should flag it as a compile time error. – ddyer Nov 28 '19 at 03:00
  • @ddyer Well no, that's not permitted by the standard. The compiler can only raise an error if it is proven that execution must use the value of a freed pointer . There is no error if it's an optional code path . The compiler could warn . – M.M Nov 28 '19 at 08:23

2 Answers2

5

The C language doesn't keep track of all the copies you make of a pointer variable.

C++ can do this with the concept of smart pointers, however C can't because it doesn't have functionality similar to a destructor. It's up to the programmer to keep track of any references and manage them appropriately.

One way to do this is to implement reference counting, which is not a trivial undertaking. You would need to create a structure that would act as a header for all allocated memory segments that holds the current reference count. You would then need to create a wrapper for malloc and family that allocates space for the header plus the requested space, write to the header at the start of the memory block, then return a pointer to the memory just after the block. You then need a function that can increase the reference count that you call manually when you make a new reference. Then you need a function to decrease the reference count which would call free when it hits 0.

Of course, even if you do this, you trade knowing when not to call free with remembering to increase/decrease the reference count where necessary. A failure to increment means using freed memory and/or a double free, while a failure to decrement means a memory leak.

dbush
  • 205,898
  • 23
  • 218
  • 273
1

In this function

bool test_safe_free() {
    struct A *a = (struct A *) malloc(sizeof(struct A));
    struct B *b = (struct B *) malloc(sizeof(struct B));
    b->arr = (int *) malloc(100 * sizeof(int));
    b->len = 100;
    a->b = b;

    safe_free_A(&a);
    return a == NULL && b == NULL;
}

you declared the pointer b that is not passed to safe_free_B. It is the pointer a->b that is by reference passed to the function safe_free_B.

safe_free_B(&((*a_ref)->b));

So the local variable b declared in the function test_safe_free stays unchanged.

You could write in the function

a->b = b;
b = NULL;

safe_free_A(&a);
return a == NULL;

That is the owner of the allocated memory now is the pointer a->b not the pointer b.

Nevertheless the functions safe_free_B and safe_free_A are not safe.:)

You could write them like

void safe_free_B(struct B **b_ref) {
    if ( *b_ref != NULL ) free((*b_ref)->arr;
    free(*b_ref);
    *b_ref = NULL;
}

and

void safe_free_A(struct A **a_ref) {
    // Before freeing A, freeing B:
    if ( *a_ref != NULL ) safe_free_B(&((*a_ref)->b));
    free(*a_ref);
    *a_ref = NULL;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Ah, yes, of course, generally "safe" means null-safety. I guess I should edit it. Thanks. Regarding setting `b` to NULL, I'm not sure I can implement it in my source code, but will definitely try it, thanks again! – M. Samil Atesoglu Nov 26 '19 at 21:08