There seem to be two arguments why one should set a pointer to NULL
after freeing them.
Short
There's no guarantee that the program crashes when accessing the NULL pointer.
Maybe not by the standard, but you'd be hard-pressed to find an implementation which does not define it as an illegal operation that causes a crash or exception (as appropriate to the runtime environment).
These problems are most often only symptoms for a much deeper problem. This can occur for all resources that require aquisition and a later release, e.g. memory, files, databases, network connections, etc. The core problem is that you've lost track of the resource allocations by a missing code structure, throwing random mallocs and frees all over the code base.
Organize the code around DRY - Don't Repeat Yourself. Keep related things together. Do one thing only, and do it good. The "module" that allocates a resource is responsible for releasing it and has to provide a function for doing so that keeps care for the pointers, too. For any specific resource, you then have exactly one place where it is allocated and one place where it is released, both close together.
Say you want to split a string into substrings. Directly using malloc(), your function has to care for everything: Analysing the string, allocate the right amount of memory, copy the substrings there, and and and. Make the function complicated enough, and it is not the question if you will lose track of the resources, but when.
Your first module takes care for the actual memory allocation:
void *MemoryAlloc (size_t size)
void MemoryFree (void *ptr)
There's your only place in your whole codebase where malloc() and free() are called.
Then we need to allocate strings:
StringAlloc (char **str, size_t len)
StringFree (char **str)
They take care that len+1 is needed and that the pointer is set to NULL when freed. Provide another function to copy a substring:
StringCopyPart (char **dst, const char *src, size_t index, size_t len)
It will take care if index and len are inside the src string and modify it when needed. It will call StringAlloc for dst, and it will care that dst is correctly terminated.
Now you can write your split function. You don't have to care for the low level details anymore, just analyse the string and get the substrings out of it. Most of the logic is now in the module where it belongs, instead of mixed together in one large monstrosity.
Of course this solution has its own problems. It provides abstraction layers, and each layer, while solving other problems, comes with its own set of them.
There isn't really a "more important" part to which of the two problems you're trying to avoid. You really, really need to avoid both if you want to write reliable software. It's also very likely that either of the above will lead to data corruption, having your webserver pwned and other fun along those lines.
There is also another important step to keep in mind - setting the pointer to NULL after free'ing it is only half the job. Ideally, if you are using this idiom, you should also wrap pointer access in something like this:
if (ptr)
memcpy(ptr->stuff, foo, 3);
Just setting the pointer itself to NULL will only have the program crash in inopportune places, which is probably better than silently corrupting data but it's still not what you want.
I don't do this. I don't particularly remember any bugs that would have been easier to deal with if I did. But it really depends on how you write your code. There are approximately three situations where I free anything:
In the third case, you set the pointer to NULL. That's not specifically because you're freeing it, it's because the whatever-it-is is optional, so of course NULL is a special value meaning "I haven't got one".
In the first two cases, setting the pointer to NULL seems to me to be busy work with no particular purpose:
int doSomework() {
char *working_space = malloc(400*1000);
// lots of work
free(working_space);
working_space = NULL; // wtf? In case someone has a reference to my stack?
return result;
}
int doSomework2() {
char * const working_space = malloc(400*1000);
// lots of work
free(working_space);
working_space = NULL; // doesn't even compile, bad luck
return result;
}
void freeTree(node_type *node) {
for (int i = 0; i < node->numchildren; ++i) {
freeTree(node->children[i]);
node->children[i] = NULL; // stop wasting my time with this rubbish
}
free(node->children);
node->children = NULL; // who even still has a pointer to node?
// Should we do node->numchildren = 0 too, to keep
// our non-existent struct in a consistent state?
// After all, numchildren could be big enough
// to make NULL[numchildren-1] dereferencable,
// in which case we won't get our vital crash.
// But if we do set numchildren = 0, then we won't
// catch people iterating over our children after we're freed,
// because they won't ever dereference children.
// Apparently we're doomed. Maybe we should just not use
// objects after they're freed? Seems extreme!
free(node);
}
int replace(type **thing, size_t size) {
type *newthing = copyAndExpand(*thing, size);
if (newthing == NULL) return -1;
free(*thing);
*thing = NULL; // seriously? Always NULL after freeing?
*thing = newthing;
return 0;
}
It's true that NULL-ing the pointer can make it more obvious if you have a bug where you try to dereference it after freeing. Dereferencing probably does no immediate harm if you don't NULL the pointer, but is wrong in the long run.
It's also true that NULL-ing the pointer obscures bugs where you double-free. The second free does no immediate harm if you do NULL the pointer, but is wrong in the long run (because it betrays the fact that your object lifecycles are broken). You can assert things are non-null when you free them, but that results in the following code to free a struct which holds an optional value:
if (thing->cached != NULL) {
assert(thing->cached != NULL);
free(thing->cached);
thing->cached = NULL;
}
free(thing);
What that code tells you, is that you've got in too far. It should be:
free(thing->cached);
free(thing);
I say, NULL the pointer if it's supposed to remain usable. If it isn't usable any more, best not to make it falsely appear to be, by putting in a potentially-meaningful value like NULL. If you want to provoke a page fault, use a platform-dependent value which isn't dereferancable, but which the rest of your code won't treat as a special "everything is fine and dandy" value:
free(thing->cached);
thing->cached = (void*)(0xFEFEFEFE);
If you can't find any such constant on your system, you may be able to allocate a non-readable and/or non-writeable page, and use the address of that.
In C++ could catch both by implementing your own smart pointer (or deriving from existing implementations) and implementing something like:
void release() {
assert(m_pt!=NULL);
T* pt = m_pt;
m_pt = NULL;
free(pt);
}
T* operator->() {
assert(m_pt!=NULL);
return m_pt;
}
Alternatively, in C you could at least provide two macros to the same effect:
#define SAFE_FREE(pt) \
assert(pt!=NULL); \
free(pt); \
pt = NULL;
#define SAFE_PTR(pt) assert(pt!=NULL); pt
The second one is way more important: re-using a freed pointer can be a subtle error. Your code keeps right on working, and then crashes for no clear reason because some seemingly unrelated code wrote in the memory that the re-used pointer happens to be pointing at.
I once had to work on a really buggy program someone else wrote. My instincts told me that many of the bugs were related to sloppy attempts to keep using pointers after freeing the memory; I modified the code to set the pointers to NULL after freeing the memory, and bam, the null pointer exceptions started coming. After I fixed all the null pointer exceptions, suddenly the code was much more stable.
In my own code, I only call my own function that is a wrapper around free(). It takes a pointer-to-a-pointer, and nulls the pointer after freeing the memory. And before it calls free, it calls Assert(p != NULL);
so it still catches attempts to double-free the same pointer.
My code does other things too, such as (in the DEBUG build only) filling memory with an obvious value immediately after allocating it, doing the same right before calling free()
in case there is a copy of the pointer, etc. Details here.
EDIT: per a request, here is example code.
void
FreeAnything(void **pp)
{
void *p;
AssertWithMessage(pp != NULL, "need pointer-to-pointer, got null value");
if (!pp)
return;
p = *pp;
AssertWithMessage(p != NULL, "attempt to free a null pointer");
if (!p)
return;
free(p);
*pp = NULL;
}
// FOO is a typedef for a struct type
void
FreeInstanceOfFoo(FOO **pp)
{
FOO *p;
AssertWithMessage(pp != NULL, "need pointer-to-pointer, got null value");
if (!pp)
return;
p = *pp;
AssertWithMessage(p != NULL, "attempt to free a null FOO pointer");
if (!p)
return;
AssertWithMessage(p->signature == FOO_SIG, "bad signature... is this really a FOO instance?");
// free resources held by FOO instance
if (p->storage_buffer)
FreeAnything(&p->storage_buffer);
if (p->other_resource)
FreeAnything(&p->other_resource);
// free FOO instance itself
free(p);
*pp = NULL;
}
Comments:
You can see in the second function that I need to check the two resource pointers to see if they are not null, and then call FreeAnything()
. This is because of the assert()
that will complain about a null pointer. I have that assert in order to detect an attempt to double-free, but I don't think it has actually caught many bugs for me; if you want to leave out the asserts, then you can leave out the check and just always call FreeAnything()
. Other than the assert, nothing bad happens when you try to free a null pointer with FreeAnything()
because it checks the pointer and just returns if it was already null.
My actual function names are rather more terse, but I tried to pick self-documenting names for this example. Also, in my actual code, I have debug-only code that fills buffers with the value 0xDC
before calling free()
so that if I have an extra pointer to that same memory (one that doesn't get nulled out) it becomes really obvious that the data it's pointing to is bogus data. I have a macro, DEBUG_ONLY()
, which compiles to nothing on a non-debug build; and a macro FILL()
that does a sizeof()
on a struct. These two work equally well: sizeof(FOO)
or sizeof(*pfoo)
. So here is the FILL()
macro:
#define FILL(p, b) \
(memset((p), b, sizeof(*(p)))
Here's an example of using FILL()
to put the 0xDC
values in before calling:
if (p->storage_buffer)
{
DEBUG_ONLY(FILL(pfoo->storage_buffer, 0xDC);)
FreeAnything(&p->storage_buffer);
}
An example of using this:
PFOO pfoo = ConstructNewInstanceOfFoo(arg0, arg1, arg2);
DoSomethingWithFooInstance(pfoo);
FreeInstanceOfFoo(&pfoo);
assert(pfoo == NULL); // FreeInstanceOfFoo() nulled the pointer so this never fires