When we started developing Elephant Memory Manager we had to be sure we covered the standards. C malloc/free (and realloc) and C++ new/delete have subtle variations in how they deal with various allocation conditions.
When it comes to implementing your own allocator, using the default system allocator or any other option, it is good to know the quirks and pitfalls of each. This way you can quickly resolve issues that rely on the standards – no matter how silly they may seem*.
Where this becomes important is when you redirect calls to malloc and new to your own allocation routines. Because each act slightly differently some libraries can fail where they once worked fine. Sometimes this can be painful to track down.
Malloc and New
C++ new should always return a valid address. When it can’t the standard dictates that it should throw an exception. Most games don’t use exceptions. Some platforms don’t even support them correctly. As a fallback, most often new will return NULL instead. The nice thing with new is, assuming you have the memory, you can forego any error checking. You know the pointer is valid and that it has allocated the required size. It is not possible to allocate 0 bytes.
C malloc is slightly different. Malloc returns NULL when an error occurs. Unlike new, no error is immediately raised and checking for a valid pointer is recommended. Generally in a game, an assert does the job. Where malloc gets complicated is with malloc(0). The standard dictates that it can return a valid pointer OR a NULL pointer. The valid memory pointer is often an allocation of around 16 bytes as well, so 0 byte allocations actually consume memory! NULL is fairly counter productive if you think about it as it generally signifies an error, often out of memory. A malloc(0) pointer still has to be removed by calling free as well. Thus free(malloc(0)) will always function regardless of the malloc return.
Why would you want a malloc of 0 bytes? Good question and one I have no answer for. What I can tell you however, is that quite a few pieces of middleware try to allocate 0 bytes and expect a valid pointer returned. Here we recommend that you return some valid pointer. Returning NULL will have you scratching your head for one of the more common pieces of middleware out there when it crashes in no mans land.
In my experience a malloc(0) always returns a valid pointer on all platforms. It has been this way for the last 10 years but that shouldnt be relied on. This maybe because most use DLMalloc or a variation of for their core allocation. The valid pointer should not be accessed either according to the standard. This makes sense as some allocator’s return a special address for this situation.
Free and Delete
In both standards these functions agree with one another. free(NULL) and delete NULL will do nothing. Most people implement each free/delete like so:
pPointer = NULL;
The thing is that the conditional is totally redundant. You are just making work for yourself. Where I think this arose from is a poor understanding of the standards in custom replacements. This meant the conditional was needed to avoid anything nasty happening.
So do you still need SAFE_DELETE? When just using delete/free I would argue that you don’t. At least you do not need the conditional test but the clearing of the pointer is always helpful. I would say that is one for your coding standards. This may not be safe practice for ->Release() style functionality, where a form of SAFE_DELETE may still be required.
Realloc is fairly simple but one that catches people out. realloc(NULL,) functions exactly like malloc. It will return a valid pointer . realloc(ptr, size) should resize the allocation though it may relocate the pointer. realloc(ptr, 0) works just like free. In all error conditions the standard says that the original pointer should remain untouched.
With realloc(malloc(0), 0) here NULL or a valid pointer could be returned from malloc. If NULL is returned from malloc then you should get a NULL out of realloc. If a valid pointer is returned then it should call free and return NULL. That is the standard as far as we have been able to ascertain. This means that regardless of the malloc(0) return you should always get NULL out. Note that I say should!
How did we implement it?
I think malloc(0) is a bit silly*. The C++ new standard change would seem to agree. We allow it, if and only if you specify that is what you want. Otherwise we raise an error immediately. We allow this on a heap by heap basis. When it is allowed we allocate 16bytes and return it as a valid address. This works with all known and tested libraries across many systems. Returning NULL does not.
DLMalloc returns a valid pointer that consumes 16 bytes of memory each time for an allocation of 0bytes as well. The standard system provided allocators all return valid pointers as well to our knowledge.
We also raise an error on NULL frees as well. Again this is customisable, but why would you ever want to try to free a NULL pointer? Generally if this happens an error has occurred further up the chain. Here, actually breaking away from the standard can aid debugging. Every allocation normally has a matching deallocation so it doesn’t, in most situations, make sense to silently allow this condition.
When it comes to running out of memory a NULL return should raise an error in a lot of situations. Again customisation is the key. If you are streaming data you may very well depend on allocations failing. This way you know you need to make room or perform some other function to free memory. In this situation the standards can cause issues.
I remember being totally shocked when I found out malloc could allocate 0bytes. That was 12 years ago. Depending on how you deal with it can cause problems further down the road. I suspect this guy bumped into the same issue and that prompted him to ask the question.
When implementing your own versions of these functions it is good to follow the standards already laid down. It can save a lot of work when you need to integrate with other components. In other situations the standards may be a hindrance. Unless you know them though you can’t plan for them.
It’s entirely possible I have some of the standards wrong here. Just thought I would slip that disclaimer in. Realloc changed subtly from C89 to C99 for instance. What I have described here has worked with everything we have tested so I don’t think you can go far wrong.
* These are my opinions on the standards.