I have never used an ISO C library that handled free(NULL); incorrectly. It is safe to do. (That is, anything that claims to implement C89 or later. The older buggy C libraries have multitudes of other quirks you need to cater for anyway.)
When using POSIX C, the following pattern is often used for reading input line-by-line:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *line = NULL;
size_t size = 0;
ssize_t len;
while (1) {
len = getline(&line, &size, stdin);
if (len == -1)
break;
/* Do something with line.
line has len characters, including the newline (if any). */
}
/* Discard unneeded line buffer */
free(line);
line = NULL;
size = 0;
/* Do something else, possibly read another file the same way */
return EXIT_SUCCESS;
}
At the Do something with line point, it is completely safe and acceptable to steal the buffer, or even free it, if one also sets line = NULL, size = 0 . Just like for the initial line, getline() will then dynamically allocate a buffer large enough for the next line.
I regularly apply the topic at hand, in the point with comment Discard unneeded line buffer. If there was no data to read from standard input, then at that point line could well be NULL. However, it is completely safe to just free it, and ensure the pointer and the allocated size get zeroed.
But why ensure line = NULL, size = 0? Because this is a pattern that avoids a common error case: use-after-free. It costs basically nothing, and it helps with debugging when something goes b0rkb0rkb0rk.
I have seen many new C programmers, and even tutorials, that suggest using malloc() to allocate an initial buffer. That is complete waste: extra code (with bug risks) with zero benefits.
The above pattern has proven its worth to me, in that it yields code that tends to have less bugs, and is easier to debug. So, nowadays I apply this pattern extensively.
As an example, consider a highly dynamic (entries and their number change often) hash table implementation based on
struct hash_entry {
struct hash_entry *next; /* Next entry in the same table slot */
size_t hash; /* Actual hash value for this entry */
/* Payload */
};
struct hash_table {
size_t size;
size_t entries;
struct hash_entry **slot;
};
#define HASH_TABLE_INITIALIZER { 0, 0, NULL }
static inline void hash_table_init(struct hash_table *ht)
{
ht->size = 0;
ht->entries = 0;
ht->slot = NULL;
}
The idea is that the user creates a hash table using either
struct hash_table my_table = HASH_TABLE_INITIALIZER;
or
struct hash_table my_table;
hash_table_init(&my_table);
The actual slot pointer array is allocated or reallocated whenever the size of the hash table is changed; and typically that happens when the first entry is added, last entry is removed, or the ratio entries/size exceeds (so the table is grown) or drops below (so the table is shrunk) some heuristic limit.
In this scheme, each hash table slot is a pointer, and an unused slot is just a NULL pointer. (Obviously, an item with hash h is stored in the chain hanging off .slot[h % .size].) Because each hash table entry has the original hash in it, the hash table can be resized (and entries moved to their corresponding new slots) when needed. The shown one is strictly a single-theaded structure; multithreaded access requires a careful locking scheme anyway.
If you implement the operations for this (type of) hash table, you'll see how useful it is to explicitly keep unused pointers marked NULL. Here, the fact that even the slot array itself will not exist when .size == 0 does mean an "extra" check (an "extra" conditional) in the function implementations, but it also means they become much simpler. For example, you can provide a function that allows the caller to specify exactly how many slots they want, or how many new hashes they intend to add, without adding complexity to the other functions.
(This is not the most efficient hash table implementation, obviously, but it has proven to be quite acceptable and robust in real life.)
So yeah, it is definitely an useful pattern. While it is not very common, the cost (NULLifying the pointer after free()) is utterly neglible in real life, and the pattern itself helps write more robust and easier to maintain code, which to me means it is worth it.
Of course, even I avoid doing that just before returning from main or exit()ing. I fully trust the OS to clean up after the process anyway, so freeing any dynamic memory allocations (except for deleting any shared memory segments!) just before the process exits isn't part of this pattern.