How you can wind up trying to allocate zero bytes in C
One of the reactions I saw to my entry on malloc(0) being allowed
to return NULL is to wonder why you'd
ever do a zero-size allocation in the first place. Unfortunately,
it's relatively easy to stumble into this situation with simple
code in certain sorts of not particularly uncommon situations. The
most obvious one is if you're allocating memory for a variable size
object, such as a Python tuple or a JSON array. In a simple C
implementation these will typically have a fixed struct that contains
a pointer to a C memory block with either the actual elements or
an array of pointers to them. The natural way to set this up is to
write code that winds up calling 'malloc(nelems * sizeof(...))
'
or something like that, like this:
array_header * alloc_array(unsigned int nelems) { array_header *h; h = malloc(sizeof(array_header)) if (h == NULL) return NULL; /* get space for the element pointers except oops */ h->data = malloc(nelems * sizeof(void *)); if (h->data == NULL) { free(h); return NULL; } h->nelems = nelems; /* maybe some other initialization */ return h; }
(As a disclaimer, I haven't tried to compile this C code because I'm lazy, so it may contain mistakes.)
Then someone asks your code to create an empty tuple or JSON array and on some systems, things will explode because nelems will be 0 and you will wind up doing 'malloc(0)' and that malloc() will return NULL, as it's allowed to, and your code will think it's out of memory. You can obviously prevent this from happening, but it requires more code and thus requires you to have thought of the possibility.
(Allocating C strings doesn't have this problem because you always need one byte for the terminating 0 byte, but it can come up with other forms of strings where you track the length explicitly.)
One tricky bit about this code is that it will only ever go wrong in an obvious way on some uncommon systems. On most systems today, 'malloc(0)' returns a non-NULL result, usually because the allocator rounds up the amount of memory you asked for to some minimum size. So you can write this code and have it pass all of your tests on common platforms and then some day someone reports that it fails on, for example, AIX.
(It's possible that modern C linters and checkers will catch this; I'm out of touch with the state of the art there.)
As a side note, if malloc(0) returns anything other than NULL, I believe that each call is required to return a unique pointer (see eg the POSIX description of malloc()). I believe that these unique pointers don't have to point to actual allocated memory; they could point to some large reserved arena and be simply allocated in sequence, with a free() of them effectively doing nothing. But it's probably simpler to have your allocator round the size up and return real allocated memory, since then you don't have handle things like the reserved arena running out of space.
|
|