56

The function for freeing an instance of struct Foo is given below:

void DestroyFoo(Foo* foo)
{
    if (foo) free(foo);
}

A colleague of mine suggested the following instead:

void DestroyFoo(Foo** foo)
{
    if (!(*foo)) return;
    Foo *tmpFoo = *foo;
    *foo = NULL; // prevents future concurrency problems
    memset(tmpFoo, 0, sizeof(Foo));  // problems show up immediately if referred to free memory
    free(tmpFoo);
}

I see that setting the pointer to NULL after freeing is better, but I'm not sure about the following:

  1. Do we really need to assign the pointer to a temporary one? Does it help in terms of concurrency and shared memory?

  2. Is it really a good idea to set the whole block to 0 to force the program to crash or at least to output results with significant discrepancy?

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
Ferit Buyukkececi
  • 2,665
  • 2
  • 20
  • 23
  • 2
    It all depends on use-cases and what you are trying to achieve. Also note that you can call `free` with a null pointer, it simply does nothing then. – Some programmer dude Jan 07 '16 at 09:18
  • 3
    This isn't the right sort of question for stackoverflow: the answers aren't clear-cut. There's benefits to doing the things in the second block of code, but there's disadvantages too. Whether the benefits outweigh the costs is just a matter of opinion. – Paul Hankin Jan 07 '16 at 09:21
  • 1
    The second method is better and safer. You can also add `assert( foo )` in the beginning of `DestroyFoo` to catch possible wrong calls in debug version. – i486 Jan 07 '16 at 09:25
  • @PaulHankin Could you please elaborate more on the disadvantages in the second block, except being slower? – Ferit Buyukkececi Jan 07 '16 at 09:27
  • @i486 assert(foo) is great advice, thanks! – Ferit Buyukkececi Jan 07 '16 at 09:28
  • @FeritBuyukkececi it's more code, it's more complex, it's slower. Also, you need the pointer to be addressable (although usually it will be). – Paul Hankin Jan 07 '16 at 09:31
  • 1
    That will not prevent any concurrency issues. The compiler is basically free to optimize the code without the constraints of the abstract machine ("observable behaviour"). So the temp is plain nonsense and shows basic missunderstanding. C does not support concurrent execution at that level - for good reasons. – too honest for this site Jan 07 '16 at 10:00
  • @JoachimPileborg: There are some (although rare!) cases of where you'd want a condition before `free()`, e.g. Valgrind complains about such cases. Of course, in some cases, you might *WANT* Valgrind to complain (depending mostly on whether your code style checks for NULL pointers elsewhere before freeing). – Tim Čas Jan 07 '16 at 11:14
  • @Olaf: I'm pretty sure the `memset()` could be optimized out anyways, if the compiler decides that `tmpFoo` is unused after that because of the `free()`, and if both are treated as builtins. After all, as far as the abstract machine is concerned, the effect of a missing `memset()` is the same. So that removes *that* (potential) advantage, too. – Tim Čas Jan 07 '16 at 11:17
  • @TimČas: Unlikely. Maybe with LTO or if all affected functions/variables have internal linkage. Yet that still would require full static code analysis. However, `memset` was not even part of my comment, yours is completely unrelated. I talked about concurrency issues. – too honest for this site Jan 07 '16 at 11:23
  • @Olaf: I'm just saying that the `memset()` is useless as well. And the whole point of the `memset()` is for security (e.g. clearing private key from RAM) --- at least I'd assume so. And would you want to rely on very specific (and possibly changing between versions) compiler behavior for security? – Tim Čas Jan 07 '16 at 11:25
  • 1
    @TimČas: I agree the `memset` is useless. If a faulty futher access to the old object occurs, the area might already have been allocated for a different object, thus not be zeroed anymore. Private keys are special anyway and `memset`ting is useless here, too, because it can already have been read before `free`ing. – too honest for this site Jan 07 '16 at 11:29
  • 1
    As you can call `free` with a null pointer, the first function is useless. Call free directly. (or use the second method) – edc65 Jan 07 '16 at 17:03
  • 9
    Do not set the freed block to 0x00. Set it to repeated copies of 0xDEADBEEF. If you are debugging a crash and you find that you're in the middle of a block of dead beef, you know that you're inside freed memory. – Eric Lippert Jan 07 '16 at 19:12

5 Answers5

68

Do we really need to assign the pointer to a temporary one? Does it help in terms of concurrency and shared memory?

It has nothing to do concurrency or shared memory. It's pointless.

Is it really a good idea to set the whole block to 0 to force the program to crash or at least to output results with significant discrepancy?

No. Not at all.

The solution suggested by your colleague is terrible. Here's why:

  • Setting whole block to 0 achieves nothing either. Because someone is using a free()'ed block accidentally, they wouldn't know that based on the values at the block. That's the kind of block calloc() returns. So it's impossible to know whether it's freshly allocated memory (calloc() or malloc()+memset()) or the one that's been free()'ed by your code earlier. If anything it's extra work for your program to zero out every block of memory that's being free()'ed.

  • free(NULL); is well-defined and is a no-op, so the if condition in if(ptr) {free(ptr);} achieves nothing.

  • Since free(NULL); is no-op, setting the pointer to NULL would actually hide that bug, because if some function is actually calling free() on an already free()'ed pointer, then they wouldn't know that.

  • most user functions would have a NULL check at the start and may not consider passing NULL to it as error condition:

void do_some_work(void *ptr) {
    if (!ptr) {
        return; 
    }

   /*Do something with ptr here */
}

So the all those extra checks and zero'ing out gives a fake sense of "robustness" while it didn't really improve anything. It just replaced one problem with another the additional cost of performance and code bloat.

So just calling free(ptr); without any wrapper function is both simple and robust (most malloc() implementations would crash immediately on double free, which is a good thing).

There's no easy way around "accidentally" calling free() twice or more. It's the programmer's responsibility to keep track of all memory allocated and free() it appropriately. If someone find this hard to handle then C is probably not the right language for them.

P.P
  • 117,907
  • 20
  • 175
  • 238
  • 4
    I agree with this answer, except the part about not setting the pointer to NULL after. With NULL assigment double free is at least consistent. Without it double free results in UB, and it **may fail silently**. With NULL assigned to pointer free-ing can at least detected later. Not assigning NULL only gives false sense of security (you expect crash which might not come). – user694733 Jan 07 '16 at 10:17
  • 3
    Some of your comments are counterpart. Because it is a good practice to check if the pointer is not NULL before "doing something with ptr" you should set ptr to NULL after freeing it. This will save lot of hours spent debugging horrible errors when you free memory, not set ptr to NULL and then someone else will (re)allocate the same region of memory. This will also prevent double free as you won't be able to free ptr which is set to NULL. – codewarrior Jan 07 '16 at 10:18
  • 1
    I consider it (`free(ptr); ptr=NULL;`) no better than not setting a pointer to NULL. If some code is *using* is a pointer that's been free()'ed that's a *bug*. I agree it may be useful in some situations to find a bug easily. If ptr == NULL is an error condition then yes, It's useful. Otherwise, no. So that entirely depends on how the rest of code handles it. I wouldn't say it's outright better (or worse). – P.P Jan 07 '16 at 10:27
  • 2
    Most user functions may have a NULL-check at the start, but only to crash fast (and often only in debug-mode): If you cannot rely on the caller following the contract, you already have lost, as there are too many untestable ways he can fail in his duty. See [Is it reasonable to null guard every single dereferenced pointer?](http://programmers.stackexchange.com/questions/216289/is-it-reasonable-to-null-guard-every-single-dereferenced-pointer) And yes, it is explicitly part of the contract of most deallocation functions to accept NULL or some other suitable "not an object"-marker. – Deduplicator Jan 07 '16 at 11:12
  • Instead of setting to 0 you could set the to-be-freed memory to 0xfeeefeee to signify more obviously that it is freed memory. Running in debug-mode may do that for you already though. – ratchet freak Jan 07 '16 at 16:32
  • "It has nothing to do concurrency or shared memory": I don't agree. I suspect the author of this complex DestroyFoo() intended this: if the pointer is used concurrently, we'd rather want to fail fast (seg fault in case of dereferencing NULL) instead of silently using invalid data (data structure being set to 0). – Eduard Wirch Jan 13 '16 at 06:43
  • 1
    @EduardWirch It can't be used concurrently without a *lock* of some sort or synchronization mechanism (C11 mutexes, pthread mutexes for threads or shared memory etc). *If* the pointer is used concurrently, there has to be a lock/sychronization in *every* place it's used. Also, a lock is needed in DestroyFoo() function to safely modify the pointer. Since it's not used inside this function, if we assume a lock is obtained when calling this function then: either others see all changes to the pointer or nothing and again, all the issues I mentioned still apply. – P.P Jan 13 '16 at 08:18
9

What your collegue suggests will make the code "safer" in case the function is called twice (see sleske comment...as "safer" may not mean the same for everybody...;-).

With your code, this will most likely crash:

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
DestroyFoo(foo); // will call free on memory already freed

With your collegues's version of the code, this will not crash:

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(&foo);
DestroyFoo(&foo); // will have no effect

Now, for this specific scenario, doing tmpFoo = 0; (within DestroyFoo) is enough. memset(tmpFoo, 0, sizeof(Foo)); will prevent crash if Foo has extra attributes that could be wrongly accessed after memory is released.

So I would say yes, it may be a good practice to do so....but it's only a kind of security against developers who have bad practices (because there's definitely no reason to call DestroyFoo twice without reallocating it)...in the end, you make DestroyFoo "safer" but slower (it does more stuff to prevent bad usage of it).

jpo38
  • 20,821
  • 10
  • 70
  • 151
  • 19
    Actually, I'd say it makes the code less safe, by hiding a bug. If you accidentally double-free, you _want_ it to crash. Note that double-freeing is not guaranteed to crash - that's the best case. It may also corrupt your memory allocator and overwrite all your stuff (undefined behavior). – sleske Jan 07 '16 at 09:30
  • @sleske That's true (except if you intentionaly may call `DestroyFoo` twice from two different piece of code/statements). But it definitely hides a potential bug due to bad programming practice at upper layers....I totally agree. – jpo38 Jan 07 '16 at 09:33
  • 3
    Yes, if you decide you want to explicitly support double-freeing, then it's not a bug by definition; then you'd need a wrapper to allow it. But that seems a problematic design to me; there should be one specific point where an object gets disposed of. Even in that case, the established method is to null the pointer after freeing it (because `free` will do nothing for a null pointer). – sleske Jan 07 '16 at 09:36
  • 3
    @sleske, jpo38: Double-freeing an allocated object very likely means you still used it after the first `free`. This is undefined behaviour. Once you `free`d an object, you must not access it anymore. Things are different if you use some kind of garbage-collection where you only free if no other reference exists. But for that you need additional measures like a reference counter (see Python for an example). Still you only call `free` if **no** access can be done to the object. – too honest for this site Jan 07 '16 at 10:05
  • @sleske: One could perhaps make it *ALWAYS* crash via a (admittedly ugly, and potentially dangerous) hack: `static char guard; assert(*foo != &guard); free(*foo); *foo = &guard;`. But I think just using a "traditional" `free_data(T*)` instead of `free_data(T**)` is better, since a double free typically also implies deref after free in other places anyways (and this doesn't help with that). – Tim Čas Jan 07 '16 at 11:23
  • @TimČas: Yes, that would work, but if you go that far, you should probably use some kind of memory checking tool/library, which automatically performs these checks (and more). glibc actually has this built-in (activated by settin `MALLOC_CHECK`); or something like Valgrind's Memcheck. – sleske Jan 07 '16 at 12:00
  • @sleske: I prefer Valgrind for this sort of stuff, but that doesn't exist in Windows. As I said, I think it's still better to just make it a *normal* freeing function regardless. – Tim Čas Jan 07 '16 at 13:01
  • @TimČas Lots of options exist on Windows for debugging heap issues. For example: https://msdn.microsoft.com/en-us/library/974tc9t1.aspx or http://unicomsi.com/products/purifyplus/ – Andrew Henle Jan 07 '16 at 17:21
4

The second solution seems to be over engineered. Of course in some situation it might be safer but the overhead and the complexity is just too big.

What you should do if you want to be on a safe side is setting the pointer to NULL after freeing memory. This is always a good practice.

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
foo = NULL;

What is more, I don't know why people are checking if the pointer is NULL before calling free(). This is not needed as free() will do the job for you.

Setting memory to 0 (or something else) is only in some cases a good practice as free() will not clear memory. It will just mark a region of memory to be free so that it can be reused. If you want to clear the memory, so that no one will be able to read it you need to clean it manually. But this is quite heavy operation and that's why this shouldn't be used to free all the memory. In most cases freeing without clearing is just enough and you don't have to sacrifice performance to do unnecessary operation.

codewarrior
  • 1,269
  • 1
  • 9
  • 14
  • "If you want to clear the memory, so that no one will be able to read it you need to clean it manually. " This is a bit misleading. On all current multi-user OSes, the OS will make sure that no other process gets the contents of freed memory (usually by zeroing it before handing it to a different process). So there's no need to worry about data leaking to a different process. – sleske Jan 07 '16 at 09:28
  • I was trying to say that free() will not clear memory, but rather mark it as unused. So if you want to really clear memory, you should do it yourself. What is more, there are ways of dumping memory of currently running process, so if your memory is not cleared someone will be able to read it. – codewarrior Jan 07 '16 at 09:38
  • True, but on modern OSes this requires running with the same (or higher) privileges as the running process, and then it's a wash. The point is that this is only a problem under certain threat models, so it may well be pointless, thus zeroing memory is _not_ always a good practice. – sleske Jan 07 '16 at 09:42
  • I agree that this is not the thing you should worry about while freeing all kinds of memory. In fact I wrote about over engineering the code and doing lot of unneeded stuff. The note about clearing memory was just a reference to original question and there was memory being set to 0 in one of the solutions. – codewarrior Jan 07 '16 at 09:46
  • @sleske Zeroing the memory is not always a good practice because it may be quite unnecessary in most applications. Is there any other reason why it may not be a good practice? – Ferit Buyukkececi Jan 07 '16 at 09:48
  • @codewarrior If memset will remain, I guess I have to check for NULL pointers, no? – Ferit Buyukkececi Jan 07 '16 at 09:50
  • Not really. What is important is to set pointer to NULL after calling free(). If this is done, next time free() will do nothing as you will pass a NULL pointer to it. This is the way of avoiding double-free errors. – codewarrior Jan 07 '16 at 09:52
  • 5
    FYI: `memset` before `free` will be optimized out by any self-respecting compiler. You want a function like `SecureZeroMemory` which is explicitly designed for *not* being optimized out for that. – Deduplicator Jan 07 '16 at 11:16
1
void destroyFoo(Foo** foo)
{
    if (!(*foo)) return;
    Foo *tmpFoo = *foo;
    *foo = NULL;
    memset(tmpFoo, 0, sizeof(Foo));
    free(tmpFoo);
}

Your colleague code is bad because

  • it will crash if foo is NULL
  • there's no point in creating additional variable
  • there's no point in setting the values to zeros
  • freeing a struct directly doesn't work if it contain things that has to be freed

I think what your colleague might have in mind is this use-case

Foo* a = NULL;
Foo* b = createFoo();

destroyFoo(NULL);
destroyFoo(&a);
destroyFoo(&b);

In that case, it should be like this. try here

void destroyFoo(Foo** foo)
{
    if (!foo || !(*foo)) return;
    free(*foo);
    *foo = NULL;
}

First we need to take a look at Foo, let's assume that it looks like this

struct Foo
{
    // variables
    int number;
    char character;

    // array of float
    int arrSize;
    float* arr;

    // pointer to another instance
    Foo* myTwin;
};

Now to define how it should be destroyed, let's first define how it should be created

Foo* createFoo (int arrSize, Foo* twin)
{
    Foo* t = (Foo*) malloc(sizeof(Foo));

    // initialize with default values
    t->number = 1;
    t->character = '1';

    // initialize the array
    t->arrSize = (arrSize>0?arrSize:10);
    t->arr = (float*) malloc(sizeof(float) * t->arrSize);

    // a Foo is a twin with only one other Foo
    t->myTwin = twin;
    if(twin) twin->myTwin = t;

    return t;
}

Now we can write a destroy function oppose to the create function

Foo* destroyFoo (Foo* foo)
{
    if (foo)
    {
        // we allocated the array, so we have to free it
        free(t->arr);

        // to avoid broken pointer, we need to nullify the twin pointer
        if(t->myTwin) t->myTwin->myTwin = NULL;
    }

    free(foo);

    return NULL;
}

Test try here

int main ()
{
    Foo* a = createFoo (2, NULL);
    Foo* b = createFoo (4, a);

    a = destroyFoo(a);
    b = destroyFoo(b);

    printf("success");
    return 0;
}
Khaled.K
  • 5,828
  • 1
  • 33
  • 51
0

Unfortunately, this idea is just not working.

If the intent was to catch double free, it is not covering cases like the following.

Assume this code:

Foo *ptr_1 = (FOO*) malloc(sizeof(Foo));
Foo *ptr_2 = ptr_1;
free (ptr_1);
free (ptr_2); /* This is a bug */

The proposal is to write instead:

Foo *ptr_1 = (FOO*) malloc(sizeof(Foo));
Foo *ptr_2 = ptr_1;
DestroyFoo (&ptr_1);
DestroyFoo (&ptr_2); /* This is still a bug */

The problem is that the second call to DestroyFoo() will still crash, because ptr_2 is not reset to NULL, and still point to memory already freed.

Marc Alff
  • 8,227
  • 33
  • 59