25

In C and C++, free(my_pointer) crashes when it is called twice.

Why? There is bookkeeping of every malloc along with the size. When the first free is called then it identifies that this was allocated with what size that's why we need not to pass size along with free call.

Since it knows every thing why doesn't it check the second time around and do nothing?

Either I don't understand malloc/free behavior or free is not safely implemented.

dandan78
  • 13,328
  • 13
  • 64
  • 78
vikas jain
  • 309
  • 1
  • 4
  • 4
  • 1
    Can you post a code snippet of where the error occurs? – ChadNC Jun 25 '10 at 11:38
  • 2
    Why the downvote? Not sure if it's a dupe, but this is a really good question. – Ben Zotto Jun 25 '10 at 12:02
  • 4
    C is a language that values speed and programmer control over guaranteed safety. You are expected to develop habits that will keep you safe rather than doing random things and hoping to be caught. Even if it were possible to do the bookkeeping you want, it would be slower, and I don't want to pay the speed price to keep other devs (we brace bracket types tend to think it's always other devs who need safety nets) safe. – Kate Gregory Jun 25 '10 at 12:51
  • Do you want `malloc` to never reuse space that was once allocated? You can achieve this yourself easily by never calling `free`. – n. m. could be an AI Sep 08 '15 at 05:26
  • @KateGregory why is then freeing a `NULL` pointer allowed? – el.pescado - нет войне Sep 08 '15 at 05:51
  • It knows the first time around because it created a bookkeeping entry. But how can it know the second time around, after the first call freed the bookkeeping? – David Schwartz Sep 08 '15 at 07:00
  • @DavidSchwartz that's right. After freeing, there is nothing there to tell it how to do the free if it was asked to do so again – Kate Gregory Sep 08 '15 at 13:51
  • 1
    @el.pescado there is a special exception for NULL because the language was designed that way. free(NULL) does nothing, so you can do it as much as you like. – Kate Gregory Sep 08 '15 at 13:52
  • @KateGregory but *why* it was designed that way? – el.pescado - нет войне Sep 08 '15 at 14:28
  • 1
    @el.pescado I wasn't there (I'm not **that** old) but my guess would be that "nothing" is a super fast thing to do, and since free(NULL) makes no sense, just doing nothing is a legitimate choice of how to handle it. – Kate Gregory Sep 08 '15 at 14:38
  • 1
    @el.pescado @KateGregory: the C99 rationale for `free()` states that: _The null pointer is specified as a valid argument to this function to reduce the need for special-case coding_. Note that `realloc(p, 0)` (and `malloc(0)`, and `calloc` with either parameter 0) may return a null pointer. – ninjalj Jan 12 '16 at 19:04

5 Answers5

34

You are not allowed to call free on unallocated memory, the standard states that quite clearly (slightly paraphrased, my emphasis):

The free function causes the space pointed to by its argument to be deallocated, that is, made available for further allocation. If the argument is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined.

What happens, for example, if the address you're double-freeing has been reallocated in the middle of a new block and the code that allocated it just happened to store something there that looked like a real malloc-block header? Like:

 +- New pointer    +- Old pointer
 v                 v
+------------------------------------+
|                  <Dodgy bit>       |
+------------------------------------+

Chaos, that's what.

Memory allocation functions are a tool just like a chainsaw and, provided you use them correctly, you should have no problems. If you misuse them, however, the consequences are your own fault, either corrupting memory or worse, or cutting off one of your arms :-)


And regarding the comment:

... it can communicate gracefully to enduser about the doubling free the same location.

Short of keeping a record of all malloc and free calls to ensure you don't double-free a block, I can't see this as being workable. It would require a huge overhead and still wouldn't fix all the problems.

What would happen if:

  • thread A allocated and freed memory at address 42.
  • thread B allocated memory a address 42 and started using it.
  • thread A freed that memory a second time.
  • thread C allocated memory a address 42 and started using it.

You then have threads B and C both thinking they own that memory (these don't have to be threads of execution, I'm using the term thread here as just a piece of code that runs - it could all be in the one thread of execution but called sequentially).

No, I think the current malloc and free are just fine provided you use them properly. By all means give some thought to implementing your own version, I see nothing wrong with that but I suspect you'll run into some pretty thorny performance issues.


If you do want to implement your own wrapper around free, you can make it safer (at the cost of a little performance hit), specifically with something like the myFreeXxx calls below:

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

void myFreeVoid (void **p) { free (*p); *p = NULL; }
void myFreeInt  (int  **p) { free (*p); *p = NULL; }
void myFreeChar (char **p) { free (*p); *p = NULL; }

int main (void) {
    char *x = malloc (1000);
    printf ("Before: %p\n", x);
    myFreeChar (&x);
    printf ("After:  %p\n", x);
    return 0;
}

The upshot of the code is that you can call myFreeXxx with a pointer to your pointer and it will both:

  • free the memory; and
  • set the pointer to NULL.

That latter bit means that, if you try to free the pointer again, it will do nothing (because freeing NULL is specifically covered by the standard).

It won't protect you from all situations, such as if you make a copy of the pointer elsewhere, free the original, then free the copy:

char *newptr = oldptr;
myFreeChar (&oldptr);     // frees and sets to NULL.
myFreeChar (&newptr);     // double-free because it wasn't set to NULL.

If you're up to using C11, there's a better way than having to explicitly call a different function for each type now that C has compile time function overloading. You can use generic selections to call the correct function while still allowing for type safety:

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

void myFreeVoid (void **p) { free (*p); *p = NULL; }
void myFreeInt  (int  **p) { free (*p); *p = NULL; }
void myFreeChar (char **p) { free (*p); *p = NULL; }
#define myFree(x) _Generic((x), \
    int** :  myFreeInt,  \
    char**:  myFreeChar, \
    default: myFreeVoid  )(x)

int main (void) {
    char *x = malloc (1000);
    printf ("Before: %p\n", x);
    myFree (&x);
    printf ("After:  %p\n", x);
    return 0;
}

With that, you simply call myFree and it will select the correct function based on the type.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • that's true..we are not allowed to call free twice...but I see a scope of improvement in free() implementaion..rather than crashing..it can communicate gracefully to enduser about the doubling free the same location as it has bookkeeping.... am i right ? – vikas jain Jun 25 '10 at 11:54
  • 1
    I doubt there's enough bookkeeping in most implementations (certainly not the ones I've seen) to guarantee that operation. And if you introduce enough, it will almost certainly have performance implications. See the update but the basic synopsis is that the current approaches balance speed and safety well. – paxdiablo Jun 25 '10 at 12:18
  • 1
    And apart from the possible performance impact, the moment something fishy is going on with heap data structures, the C runtime should abort before letting some malware mess with code execution paths. – ninjalj Jun 25 '10 at 12:44
  • 3
    @vikas If we wanted `free` to check for double deallocation, the implementation of `free` would need to keep track of every address which was ever freed, and check all of those every time. Not to mention dealing sanely with addresses that were once freed and then later re-allocated with `malloc`. You're essentially suggesting that the standard C library behave like valgrind. Run a program through valgrind sometime and see how fast it goes. That's why this sort of bookkeeping is not done unless you ask for it. – Tyler McHenry Jun 25 '10 at 12:49
  • @paxdiablo. you said "What happens, for example, if the address you're double-freeing has been reallocated in the middle of a new block and the code that allocated it just happened to store something there that looked like a real malloc-block header?". I wonder why would a malloc call pick up a free block from the middle. malloc always picks up the free block from the head, doesn't it? – zhuang Nov 03 '13 at 21:53
  • @lysisius, blocks locks can be coalesced so that the memory at address 1234 may be given out but, when freed, it may become _part_ of a free block at address 1200. Nothing in C mandates how malloc manages the arena - it _could_ give out memory in the middle of a free block. But that's not what I was getting at. If the freed mem at 1234 was then given out as the second part of a new allocation, and the owner put something there that looked like malloc control info, freeing 1234 would most likely be undetectable and almost certainly catastrophic. – paxdiablo Nov 03 '13 at 23:55
  • 1
    @Tingya: because, being a pass by value language, you can't change the passed-in pointer and have that reflected to the caller, without doing another level of indirection. Which you can do yourself but it will probably introduce a speed penalty, however small. One of C's main presumptions is: trust the programmer. In the interests of showing how it's done, I've added that to the answer. – paxdiablo Sep 08 '15 at 06:18
10

You might be misinterpreting its behavior. If it crashes right away then it is implemented in a safe manner. I can attest that this was not common behavior for free() many moons ago. The typical CRT implementation back then did no checking at all. Fast and furious, it would simply corrupt the heap's internal structure, messing up the allocation chains.

Without any diagnostic at all, the program would then misbehave or crash long after the heap corruption took place. Without having any hint why it misbehaved that way, the code that crashed wasn't actually responsible for the crash. A heisenbug, very difficult to troubleshoot.

This is not common anymore for modern CRT or OS heap implementations. This kind of undefined behavior is very exploitable by malware. And it makes your life a wholeheckofalot easier, you'll quickly find the bug in your code. It has kept me out of trouble for the past few years, haven't had to debug untraceable heap corruption in a long time. Good Thing.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
4

Good question. As you note, malloc and free usually do some form of bookkeeping, often in the few bytes preceding the allocation. But think of it this way:

  1. Malloc some memory -- adds the bookkeeping data.
  2. Free it -- memory is returned to pool.
  3. You or someone else malloc's some more memory, which might or might not include or line up with the old allocation.
  4. You free the old pointer again.

The heap (the code for malloc an free management) has at this point already lost track of and/or overwritten the bookkeeping data, because the memory has gone back to the heap!

Hence the crashes. The only way of providing this would be remembering every allocation ever made in a database somewhere, which would grow unbounded. So they don't that. Instead, remember not to double-free. :)

Ben Zotto
  • 70,108
  • 23
  • 141
  • 204
  • "The heap (the code for malloc an free management) has at this point already lost track of and/or overwritten the bookkeeping data, because the memory has gone back to the heap!"... as you said..once it is lost the information..it can be concluded that that it is already free() or wrong adress. and communicate to programmer gracefully.. but should not try to free from that location ....and crashes application...because it has not corrupted anything in heap...just sent a wrong pointer which it already validated with bookkeeping detail and found no reference to it.. – vikas jain Jun 25 '10 at 11:57
  • 2
    Ah! But if the returned memory has already been reused by someone else, then what *should* be in the bookkeeping area has been replaced by someone else's data, then free reads that data and assumes it refers to heap tracking... when in fact, it's not bookkeeping data at all. So free goes off into the weeds and crashes. – Ben Zotto Jun 25 '10 at 12:01
  • 1
    "So free goes off into the weeds and crashes". Or something worse. An attacker crafted the bookkeeping info so free() ends up overwriting some important pointer, like the SEH or the next dynamic function to be called. – ninjalj Jun 25 '10 at 12:39
3

why does not it check second time when it may not found any allocated size for second free() call

An additional check in the free() function itself would slow your program down in all correct cases. You should not be doing a double free. Managing memory is your responsibility as a programmer; failure to do so is a programming error. It's part of the philosophy of C: it gives you all the power you need, but as a consequence, makes it easy to shoot yourself in the foot.

Many C runtimes will do some checking in their debug versions, so you'll get a reasonable notification in case you're doing something wrong.

Thomas
  • 174,939
  • 50
  • 355
  • 478
1

You say:

not understood why. there is bookkeeping of every malloc() along with the size.

Not necesarilly. I'll explain a bit about dlmalloc (used in glibc, uClibc, ...).

Dlmalloc tracks blocks of free space. There cannot be two contiguous free blocks, they are merged immediately. Allocated blocks are not tracked at all! Allocated blocks have some spare space for bookkeeping info (size of this block, size of the preceding block and some flags). When an allocated block is free()'d, dlmalloc inserts it into a doubly-linked list.

Of course, all of this is better explained at this dlmalloc article

ninjalj
  • 42,493
  • 9
  • 106
  • 148