realloc() and the oversize importance of zero-size objects
The job of realloc() is to change the size of an existing allocation; its prototype is:
void *realloc(void *ptr, size_t size);
This call will replace the memory object at ptr with a new object of the given size and return a pointer to its replacement; that new object will contain the same data, up to the minimum of the old and new sizes. The old memory will be freed. Calls to realloc() are useful when an allocated block of memory needs to be resized on the fly.
An interesting question arises, though, when size is zero. If a
program calls malloc()
requesting a zero-byte object, the result is (in glibc) well defined:
"If size is 0, then malloc() returns a unique pointer
value that can later be successfully passed to free().
"
Needless to say, any attempt to store data using the returned pointer is
unlikely to lead to joy, but the caller will get a pointer value
back. This appears to be common behavior across malloc()
implementations.
The situation is not quite as clear with realloc() in either the relevant standards or existing implementations; if it is called with a size of zero, it could do any of:
- Behave like malloc() and return a pointer to a zero-byte object.
- Behave like free(), release the object, and return NULL.
- Behave like a spoiled brat, refuse to do anything, store an error number in errno, and return NULL.
Alejandro Colomar recently brought this question to the glibc development list. His opinion, expressed in unambiguous terms, was that realloc() should behave in the same way as malloc() when passed a size of zero; it should, in other words, free the passed-in object and return a pointer to a zero-byte object. But that is not what glibc's realloc() does; instead, it takes option 2 above: it frees the object and returns NULL. This behavior is not new; it dates back to 1999, when the behavior was changed to align with the C99 standard (or, at least, one reading thereof).
This change, Colomar asserted, should be reverted. That would make glibc
conformant to his interpretation of C99 and in line with what the BSD
systems do. He copied the discussion widely and rallied some support to
this cause. None other than Douglas McIlroy
put in an
appearance to describe glibc's behavior as "a diabolical invention
".
Paul Eggert agreed,
saying that "one must always be able to shrink an allocation, even if
the shrinkage is [to] zero
".
In truth, there were few defenders for glibc's behavior, but there is still resistance to changing it. As Joseph Myers pointed out, there are almost certainly programs that rely on the current behavior:
Given that any change in implementation behavior would introduce memory leaks or double frees depending on what an implementation currently does that applications rely on, it's not reasonable to try now to define it more precisely.
Colomar, though, disagreed strongly. Any memory leaks, he said, would be of zero-byte objects and could thus be ignored; Siddhesh Poyarekar pointed out, though, that a zero-byte object still requires the equivalent of two size_t values for tracking, so real memory would be leaked. Colomar also doubted the double-free case; Myers explained how that could come about. While there is disagreement over how many programs would be affected by this sort of subtle change, it is hard to argue that the number would be zero. That is going to make the glibc developers reluctant to change realloc() in this way.
Poyarekar, instead, attempted to find a compromise with this patch adding a new tunable that would allow the behavior of realloc() to be changed while leaving the current behavior as the default. Florian Weimer disliked the patch, saying that any applications that would benefit from the tunable are simply buggy in their current form; he later added that distributions would have to do a lot of work to support this tunable properly. Andres Schwab argued that a tunable is not appropriate, since a given program's needs will not change at run time; the behavior, if it changes at all, needs to be set at compile time.
Hanging over this whole discussion is another important detail: the C23
standard explicitly defines calling either malloc() or
realloc() with a size of zero as undefined behavior. That is a
change from C17, which called it implementation defined. Colomar had
mentioned that change in his initial message, but dismissed it, saying
"let's ignore that mistake
". The glibc project cannot entirely
ignore the standards it implements, though. So it is not surprising that,
among others, Poyarekar also suggested
updating the GNU tools and sanitizers to enforce this standard in the long
term.
The conversation wound down without any definitive conclusions. One might
surmise, though, that a change to the well-documented behavior of
realloc() for the last 25 years would be unlikely to happen,
even without the C23 change. Instead, glibc will have a hard time
resisting the push from the standards committee to eliminate the existence
of zero-size objects entirely. At some point, attempting to allocate such
an object may well become an error, though this behavior will surely be
controlled by a feature-test macro, as
usual, to avoid breaking existing programs. Meanwhile, though, chances are
good that we will see further ado over the allocation of nothing.
