23

I found that some people and references like books state that if p != NULL and p origins from previous allocation (e.g. by malloc), then realloc(p, 0) is equivalent to free(p) on GNU/Linux. To support this thesis man realloc states exactly in that manner (emphasis mine going forward):

The realloc() function changes the size of the memory block pointed to by ptr to size bytes. The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes. If the new size is larger than the old size, the added memory will not be initialized. If ptr is NULL, then the call is equivalent to malloc(size), for all values of size; if size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr). Unless ptr is NULL, it must have been returned by an earlier call to malloc(), calloc() or realloc(). If the area pointed to was moved, a free(ptr) is done.

As you may find in this question, the C Standard does not define precisely what should happen and actual behavior is implementation-defined. More specifically:

The C11 §7.22.3/p1 Memory management functions says:

If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.

and C11 §7.22.3.5 The realloc function contains:

3) (...) If memory for the new object cannot be allocated, the old object is not deallocated and its value is unchanged.

4) The realloc function returns a pointer to the new object (which may have the same value as a pointer to the old object), or a null pointer if the new object could not be allocated.

I wrote some basic code to find out actual behavior with help of mcheck, memory checker, that is supplied with glibc:

#include <mcheck.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int a = 5;
    int *p, *q;

    mtrace();

    p = malloc(sizeof(int));
    q = &a;

    printf("%p\n", (void *) p);
    printf("%p\n", (void *) q);

    q = realloc(p, 0);

    printf("%p\n", (void *) p);
    printf("%p\n", (void *) q);

    return 0;
}

and results are:

$ gcc -g check.c 
$ export MALLOC_TRACE=report
$ ./a.out 
0xfd3460
0x7ffffbc955cc
0xfd3460
(nil)
[grzegorz@centos workspace]$ mtrace a.out report 

Memory not freed:
-----------------
           Address     Size     Caller
0x0000000000fd3460      0x4  at /home/grzegorz/workspace/check.c:12

As you may see q was set to NULL. It seems that free() was not really called. In fact it can't be unless my interpretation is incorrect: since realloc has returned NULL pointer, the new object could not have been allocated, which implies that:

the old object is not deallocated and its value is unchanged

Is this correct?

Community
  • 1
  • 1
Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
  • 12
    The wording about `realloc(ptr, 0)` being equivalent to `free(ptr)` used to be in POSIX, but the [official POSIX `realloc` reference](http://pubs.opengroup.org/onlinepubs/9699919799/functions/realloc.html) now uses the language from the C standard. The POSIX standard does however say "if `realloc()` returns a null pointer, the space pointed to by p has not been freed". – Some programmer dude Feb 22 '15 at 16:10

4 Answers4

9

Edit: Your glibc seems to be a pre-2.18, in 2.18 a bug was fixed in mtrace (see here). On a 2.20 glibc your test program reports: "No memory leaks."

free is called in glibc. From the sources of current glibc 2.21 (here and here):

/*
  REALLOC_ZERO_BYTES_FREES should be set if a call to
  realloc with zero bytes should be the same as a call to free.
  This is required by the C standard. Otherwise, since this malloc
  returns a unique pointer for malloc(0), so does realloc(p, 0).
*/

#ifndef REALLOC_ZERO_BYTES_FREES
#define REALLOC_ZERO_BYTES_FREES 1
#endif

void *
__libc_realloc (void *oldmem, size_t bytes)
{
  mstate ar_ptr;
  INTERNAL_SIZE_T nb;         /* padded request size */

  void *newp;             /* chunk to return */

  void *(*hook) (void *, size_t, const void *) =
    atomic_forced_read (__realloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));

#if REALLOC_ZERO_BYTES_FREES
  if (bytes == 0 && oldmem != NULL)
    {
      __libc_free (oldmem); return 0;
    }
#endif
4566976
  • 2,419
  • 1
  • 10
  • 14
  • This doesn't rule out some header (perhaps optionally) defining `REALLOC_ZERO_BYTES_FREES` as zero. No header is doing so, but I think you should mention that in your answer. –  Feb 22 '15 at 21:21
  • 1
    @hvd Defining `REALLOC_ZERO_BYTES_FREES` only has an effect when you are *building* glibc. It has no effect when you are only using the library. – 一二三 Feb 23 '15 at 02:35
  • @一二三 Well duh. :) I'm referring to the headers that glibc is using itself. This isn't the complete source file. The complete source file contains `#include` directives. –  Feb 23 '15 at 07:47
7

Though my intepretation of "returned NULL" case seems to be correct (see my edit below), glibc developers decided to keep it consistent with previous C89 Standard and rejected to conform with C99/C11:

There is no way this will be changed. This is how it has been implemented forever. C should document existing practice. Changing it would mean introducing memory leaks.

Also mcheck indication was misleading, as other test-case has showed that memory is effectively freed by realloc:

#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *p, *q;

    p = malloc(20 * sizeof(int));
    malloc_stats();

    putchar('\n');

    q = realloc(p, 0);
    malloc_stats();

    return 0;
}

Here, output is:

$ gcc check.c 
$ ./a.out 
Arena 0:
system bytes     =     135168
in use bytes     =         96
Total (incl. mmap):
system bytes     =     135168
in use bytes     =         96
max mmap regions =          0
max mmap bytes   =          0

Arena 0:
system bytes     =     135168
in use bytes     =          0
Total (incl. mmap):
system bytes     =     135168
in use bytes     =          0
max mmap regions =          0
max mmap bytes   =          0

EDIT:

As hvd pointed in comment, the ISO/IEC Working Group had some discussion, materialized as Defect Report #400. The proposed changes may likely allow existing practice of glibc in the future revision of the C Standard (or possibly as Technical Corrigendum 1 for C11).

What I really like about DR #400 is proposition to:

Add to subsection 7.31.12 a new paragraph (paragraph 2):

Invoking realloc with a size argument equal to zero is an obsolescent feature.

Community
  • 1
  • 1
Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
  • 2
    +1 for citing the bug tracker. I've re-opened the issue since the decision was made by glibc's former dictator who's now moved on. – R.. GitHub STOP HELPING ICE Feb 22 '15 at 20:51
  • 2
    @R.. Is there any glibc bug, though? C changed to re-allow the C90 and glibc behaviour if I'm reading [DR #400](http://open-std.org/JTC1/SC22/WG14/www/docs/dr_400.htm) correctly. Or was that never accepted? –  Feb 22 '15 at 21:18
  • @hvd: I suppose that will be in the next TC to C11, so maybe that's enough to make people happy. I still think it's confusing though. The first change made in the proposed TC for the DR makes it sound like NULL is only a valid return value for `realloc` when an error occurred. – R.. GitHub STOP HELPING ICE Feb 22 '15 at 21:45
  • @R.. That is exactly what it means, and that's also what C99 intended. That part is a just a clarification. When `realloc` returns `NULL`, that does indicate a failure to allocate memory. However, in the past, failure to allocate memory would guarantee the previously-allocated memory would remain available. It's that last guarantee that's changed: if `size` is `0`, that's no longer guaranteed. –  Feb 22 '15 at 22:17
  • Would there be any problem with allowing `malloc`-style functions to process a zero-byte allocation request by returning the address of a static dummy object, provided that `realloc` and `free` interpret treat such an address as equivalent to a null pointer? That would allow an implementation to guarantee that `realloc(ptr,0)` will never return null and yet also allow user code to ignore its return value without creating memory leaks. – supercat May 31 '18 at 22:38
2

As the behaviour of realloc() when being passed a size of 0 is implenentation defined ...

If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.

... the portable equivalent to

void * p = malloc(1);
free(p);

needs to be

void * p = malloc(1);
p = realloc(p, 0)
free(p); /* because of the part after the "or" as quoted above.

The memory balance should be even afterwards.

Update covering realloc()'s "error" case:

void * p = malloc(1);
{
  void * q = realloc(p, 0);
  p = q ?q :p;
}
free(p); /* because of the part after the "or" as quoted above.
alk
  • 69,737
  • 10
  • 105
  • 255
2

Have a look at realloc code in glibc here : http://code.woboq.org/userspace/glibc/malloc/memusage.c.html#realloc. You will see at line 434 that free is called when size is 0.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • 4
    Are you sure this is *the* implementation of glibc's `realloc`? I find the sources quite confusing, and this source file contains the comment (in the first line) "Profile heap and stack memory usage of running program." There is another file, `malloc/malloc.c`, which contains `__libc_realloc`, which is aliased to `realloc`. That implementation in fact contains a compiler switch called `REALLOC_ZERO_BYTES_FREES` that influences the behaviour in this case. – dyp Feb 22 '15 at 16:29
  • You are right. In http://osxr.org/glibc/source/malloc/malloc.c see lines 571--574. Don't know when this is set or not... – Jean-Baptiste Yunès Feb 22 '15 at 16:37
  • Also see lines 217--220 in http://www.atnf.csiro.au/computing/software/casacore/casacore-1.2.0/doc/html/malloc_8h_source.html. Defining this symbols is a matter of consistency with `malloc` behavior on zero size allocations. – Jean-Baptiste Yunès Feb 22 '15 at 16:41