I forgot to mention my own opinion. I think that malloc(0) ought to return a 0-sized object, and likewise realloc(ptr, 0) ought to resize the object to 0. Malloc and realloc always allocating feels more consistent to me. For a practical example, I have some code that reads an entire FILE stream into memory. This requires realloc with doubling the size every time. After finishing, I'd like to resize the buffer down to the total bytes read. If it reads 0 bytes, I'd like it to resize the buffer to 0.
I think my sane_realloc never freeing has much simpler behavior. As much as I hate the needless waste of 1 byte, if my code allocates thousands of 0-sized objects, I'd rather fix that before adding complexity to my sane_realloc.
With yours solving the 1 byte problem, it still interests me. We can simplify your code slightly.
A zero-sized object is immutable, since it has no bits to mutate. The pointer cannot be dereferenced. It may be compared to other pointers. So the question is: do you want to obtain unique zero-sized objects, or are you okay with there being one representative instance of all of them? If you're okay with one, you can just call SANE_REALLOC_EMPTY that object. I called it EMPTY on purpose; it represents an empty object with no bits.
If you want unique zero-sized objects, you can always simulate them with objects of size 1. The allocator API doesn't have to make that choice internally for you.
In a Lisp implementation, I could use unique, zero sized objects to implement symbols. The gensym function would create a new one.
Zero-sized distinguishable objects can have properties attached to them via hashes. At the API level, you cannot tell how a property (e.g. symbol-name) is attached to an object.
Properties, like the symbol name, would be attached to these objects via hashes.
It's not an ideal representation for symbols when all symbols have a certain property, like name.
If you have objects such that they have certain properties, but the properties are rare (only a few objects have a value of the property that requires storing space, and for the rest it is some "undefined" value, you can achieve a sparse representation by making the objects as small as possible, attaching the properties externally.
I think my sane_realloc never freeing has much simpler behavior. As much as I hate the needless waste of 1 byte, if my code allocates thousands of 0-sized objects, I'd rather fix that before adding complexity to my sane_realloc.
With yours solving the 1 byte problem, it still interests me. We can simplify your code slightly.