BUG: realloc(p,0) should be consistent with malloc(0)
Alejandro Colomar
alx@kernel.org
Wed Jun 18 23:32:26 GMT 2025
[CC += Joseph]
Hi Wilco,
On Wed, Jun 18, 2025 at 10:11:01PM +0000, Wilco Dijkstra wrote:
> Hi Alejandro,
>
> > > And those are the 4 words that allow one to call free(p) AND return NULL.
> >
> > By 'one' you mean realloc(p,0), I guess.
> >
> > No. Anything that starts by "If size is non-zero" does NOT give any
> > allowance for what can happen if size is zero.
>
> Of course it does. It HAS to explicitly exclude zero here to allow that case,
No, it doesn't. Please ask Joseph Myers if you don't believe it. We
just talked about the same wording situation for a similar thing just
today.
It excludes zero, and later must specify what happens for zero.
If it didn't specify it later, it would be implicit Undefined Behavior.
> otherwise the zero case must do the same as for any other size, and then
> there would be no point in specifying all the errno crazyness.
The zero case behaves different, but it's not whatever you want to
interpret from implicitly not saying it. It must be (and is) explicitly
specified what happens for the case of size zero.
> Look at what it claims:
>
> "The ISO C standard makes it implementation-defined whether a call to realloc(p, 0)
> frees the space pointed to by p if it returns a null pointer because memory for the
> new object was not allocated. POSIX.1 instead requires that implementations set
> errno if a null pointer is returned and the space has not been freed, and POSIX
> applications should only free the space if errno was changed."
This is APPLICATION USAGE from POSIX.1-2024, which is a non-normative
section. It's good to mention where your quotes come from.
And yes, it means that realloc(p,0) can result in returning NULL without
setting errno, and thus passing the input pointer to free(3). However,
the only situation in which this can happen is if p is a null pointer
itself. This is explicitly said in the RETURN VALUE section, which is
normative.
RETURN VALUE
If size is 0,
...
either:
- A null pointer shall be returned and,
if ptr is not a null pointer, errno shall be set to EINVAL.
- A pointer to the allocated space shall be returned, ...
You're acting as if you didn't read that normative section, which is
very explicit in what can happen.
> So if realloc (p, 0) returns NULL AND sets errno then it must not free the block.
> However if it doesn't set errno, then it must free the block.
Correct. But this is only allowed to happen if ptr is a null pointer,
in which case, freeing is a no-op.
> Basically POSIX requires you to write something like this for every realloc:
>
> errno = 0;
> newp = realloc (oldp, size);
> if (newp == NULL)
> {
> if (size == 0 && errno == EINVAL)
> free (oldp); // only free old block if errno set
> else if (size == 0)
> // do NOT free oldp (OK for current GLIBC, memory leak for other allocators)
> else if (size != 0)
> free (oldp); // oldp valid, so free it
> }
> else
> {
> free (newp);
> }
Nope. Plus, you forgot to handle errors?
Also, do you know any existing code that does this? Otherwise, you're
acknowledging that existing code is written per the semantics I'm
proposing, and thus changing the implementation is harmless.
The only code I've seen which would be affected by our proposal is code
that implements free() by calling realloc(p, 0). That code would result
in leaks of a few bytes, which is relatively unimportant.
> > And BTW, if it has to set errno to EINVAL, it means it should not free
> > the input pointer, so it clearly is non-conforming.
>
> Setting EINVAL and freeing it would be non-conforming. But that's not what
> GLIBC does!
>
> POSIX can be fixed by removing the "size is non-zero" part and the useless
> errno handling. Then the code above just becomes:
>
> newp = realloc (oldp, size);
> if (newp == NULL)
> {
> free (oldp); // oldp was not freed (this will crash GLIBC if size == 0)
> }
> else
> {
> free (newp);
> }
This is precisely the behavior I'm suggesting. But we need *you* to fix
glibc before that can be standardized.
So, my end goal is that code should be written like this:
old = malloc(n); // n may be zero
if (old == NULL)
exit(1);
new = realloc(old, m); // m may be zero
if (new == NULL) {
free(old);
exit(1);
}
free(new);
For this, I need a two-step change:
1) Define realloc(p,n) to be equivalent to free(p) and malloc(n), plus
moving the contents, plus not freeing on error.
2) Require that malloc(0) returns non-null.
The only implementations that need to be fixed to comply with step 1 are
glibc, Bionic, and Windows, as far as I know.
There are existing implementations that need to be fixed to comply with
step 2, and I don't know how many.
So, do you agree with this goal or do you oppose? It seems you're
opposed, but now you say you'd like that.
> > Which versions of each standard are you reading? ISO C23 makes r(p,0)
> > UB, so there's nothing similar to POSIX in ISO C.
>
> https://pubs.opengroup.org/onlinepubs/9799919799/functions/realloc.html
> The quote above says ISO C, I'm assuming it means the latest version.
POSIX.1-2024 is based on ISO C17, not ISO C23.
Have a lovely day!
Alex
--
<https://www.alejandro-colomar.es/>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20250619/83f2b009/attachment-0001.sig>
More information about the Libc-alpha
mailing list