One myth less

I do write C code on a daily basis since 2001 and I don’t know why I always believed that free(NULL) would crash, so I always used the painful construction:

        if (p)
                free(p);

Until yesterday! While talking to Lennart Poettering and we bet about that. Damn, I was so sure about it, but I’d really want to believe I was wrong, since I could avoid such stupid “if”. Well, after we got David Zeuthen‘s phone we checked the free(3) man page and I was proved wrong!

Thanks Lennart, I owe you some bucks ;-)

7 Responses to One myth less

  1. It will crash on BREW (brew.qualcomm.com) but that’s just because BREW is different – you have to use FREE() in any case, and FREEIF() if you want the null pointer guard.

  2. Although I plan to do no code in BREW, it’s good to state so here.

    To be clear to others: this is about POSIX. Not every system implements it.

  3. I committed a similar mistake some days ago.

    I was sure free()/del release memory, but don’t turn it back to OS.
    But it’s not true in python. del call returns allocated memory to OS.

  4. Danilo,

    Python is something totally different.

    This post was about POSIX libC free(3) implementation accepting NULL as parameter. Since other parts of libC does not accept, mostly with good reason to (ie: strdup(3)), I just assumed that free() also had that constraint, a myth to me.

    If you pay attention to how both free(3) and strdup(3) are implemented, you can easily guess they work as expected. The former just uses the given address as an identifier for its own internal structures that maps given memory chunks (with malloc/realloc) to pages got from kernel VM, so the contents of that address is not touched. The latter, on the other hand, will use the contents of that pointer.

    As for Danilo’s comment: free(3) does not guarantee to give memory back to OS. LibC malloc/realloc/free will get pages from kernel’s VM and then slice it as requested, distributing pieces to users of malloc/realloc. When you free some given memory, it will just mark the slice as unused. If the slice was the last used of a page, this page can be released to the underlying OS, but this might not be the case in some circumstances, due various optimizations/heuristics.

    This is one of the reasons why you free() some memory and use it later and do not get a SEGV: to the kernel (VM), the page is still valid, so no signal is sent to the process.

    Just for completeness: Python is a virtual machine and objects are automatically collected when they have no references left. Default C/Python uses reference-counting and 2-phase garbage collector, one quick and other more complete to remove garbage with circular-dependency. Also by default it uses another memory allocator (on top of libC malloc) to speed up some common cases. With that in mind, when you “del object” in Python, you’re just releasing the reference to it. This reference can be the last, in this case object’s memory would be returned to python memory allocator, that can return to libC that can return to kernel! :-)

  5. Yes,but how libc release memory to kernel? Does Python use nmap to allocate memory? I don’t know another way to return unused memory to kernel without close application…

    Btw, in MacOSX, python doesn’t release memory when it loses an object reference…

  6. Danilo,

    What I tried to explain is that free(3) does not communicate directly to kernel, instead it will use an user space memory allocator, in UNIX case it’s common to use LibC’s. This user space memory allocator will in turn talk to kernel using sbrk(2) or mmap(2) syscalls, requesting both heap and anonymous memory changes.

    Python adds yet more layers to this. Default C/Python will use a more customized memory allocator on top of LibC’s, I believe that’s also the case for MacOSX.

    As for releasing memory when some object becomes unreferenced, you need to evaluate in which layer. Most of the times it will not give memory back directly to kernel, but will for sure give to the direct layer (in this case, Python’s memory allocator), which can delay propagation of such “memory released” event, in the hope someone will need this memory soon, that’s an optimization issue: every time you need to go to deeper layers, you pay a higher price.