44

I've been reading over discussions of how malloc behaves when you request a zero sized block.

I understand that the behavior of malloc(0) is implementation-defined, and it is supposed to either return a null pointer, or a non-null pointer that I am nonetheless not supposed to access. (Which makes sense, since there's no guarantee that it points to any usable memory.)

However, if a get such a nonaccessable non-null pointer, am I allowed to pass it to free in the usual way? Or is that illegal, since the pointer I get from malloc(0) may not point to an actual allocated block of memory?

Concretely, does the following code have well-defined behavior:

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

int main() {
    int* x = (int*) malloc(0);
    if (x == NULL) {
        printf("Got NULL\n");
        return 0;
    } else {
        printf("Got nonnull %p\n", x);
    }
    free(x); // is calling `free` here okay?
}
pnkfelix
  • 3,770
  • 29
  • 45
  • 19
    Yes, you can `free` the result of `malloc` – M.M Jun 02 '17 at 14:26
  • Wrong format specifier in printf though, cast to unsigned long if you want to use `lx` – M.M Jun 02 '17 at 14:27
  • 13
    "If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().", https://linux.die.net/man/3/malloc, RTFM :p. – Stargateur Jun 02 '17 at 14:27
  • yeah I'm on OS X and their man pages often aren't as good as those on Linux. – pnkfelix Jun 02 '17 at 14:29
  • 8
    You are also guaranteed to be able to pass `NULL` to `free()`. Whatever `malloc()` returns can be safely `free()`d (once). – rici Jun 02 '17 at 14:32
  • 6
    @M.M, no don't cast pointers, at all. In code like this use `void*` instead of `int*`, don't cast the result of `malloc` anyhow, and use the `%p` specifier to print pointers. – Jens Gustedt Jun 02 '17 at 14:40
  • 2
    @JensGustedt `%p` format is not defined whether it's hex, decimal, what the formatting is... there are reasons to convert pointers to integers for printing, to control the output format – M.M Jun 02 '17 at 14:43
  • 2
    @M.M, and there are good reasons that this is implementation defined, pointers are not always expressible as simple numbers. Those platforms where they are and that I know of, they use hex representation. Do you know of any other? – Jens Gustedt Jun 02 '17 at 17:06
  • 1
    The bigger problem is that casting a pointer to `unsigned long` will truncate it on LLP64 platforms like MSVC++. If you have to cast a pointer to an integer, cast it to `uintptr_t`. – dan04 Jun 02 '17 at 23:43
  • [don't cast the result of `malloc` in C](http://stackoverflow.com/q/605845/995714), and [use `%p` to print pointers](https://stackoverflow.com/q/9053658/995714). @M.M if you want to control the format, at least cast to [`uintptr_t`](https://stackoverflow.com/q/1845482/995714) and print with [`PRI?PTR`](https://stackoverflow.com/q/5795978/995714) – phuclv Jun 03 '17 at 05:34
  • somewhat ironically, my original version of the question did cast to ... well, to `intptr_t` (not `uintptr_t`), but my heart was in the right place... – pnkfelix Jun 08 '17 at 20:39

2 Answers2

43

The C99 standard (actually WG14/N1124. Committee Draft -- May 6, 2005. ISO/IEC 9899:TC2) says about malloc():

The pointer returned points to the start (lowest byte address) of the allocated space. If the space cannot be allocated, a null pointer is returned. 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 about free():

Otherwise, if the argument does not match a pointer earlier returned by the calloc, malloc, or realloc function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined.

IEEE Std 1003.1-2008 (POSIX), 2016 Edition says about free():

The free() function shall cause the space pointed to by ptr to be deallocated; that is, made available for further allocation. If ptr is a null pointer, no action shall occur. Otherwise, if the argument does not match a pointer earlier returned by a function in POSIX.1-2008 that allocates memory as if by malloc(), or if the space has been deallocated by a call to free() or realloc(), the behavior is undefined.

So, whatever *alloc() returns, you can pass to free().

As for the current implementations of malloc():

FreeBSD uses the contributed jemalloc which does

void *
je_malloc(size_t size)
{
    void *ret;
    size_t usize JEMALLOC_CC_SILENCE_INIT(0);

    if (size == 0)
        size = 1;
    [...]

Whereas Apple's libmalloc does

void *
szone_memalign(szone_t *szone, size_t alignment, size_t size)
{
    if (size == 0) {
        size = 1; // Ensures we'll return an aligned free()-able pointer
    [...]

The GLIBC also changes the requested size; it is using a call to this macro with the requested size in bytes as the parameter to align the size to a certain boundary or simply the minimum allocation size:

#define request2size(req)                                       \
    (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?         \
    MINSIZE :                                                   \
    ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
apriori
  • 1,260
  • 11
  • 29
24

Yes, in fact you must do so to avoid a probable memory leak.

The malloc system typically returns a hidden control block in the space immediately before the pointer, with information such as allocation size. If allocation size is zero, this block still exists and occupies memory, if malloc returns non-null.

Malcolm McLean
  • 6,258
  • 1
  • 17
  • 18
  • I see. (I had assumed it might use a sentinel block for zero-sized allocations, but I can believe that is not a universal choice. Looking at the Linux man page, the use of the phrase "or a unique pointer value" leads me to wonder if using a sentinel would even be legal...) – pnkfelix Jun 02 '17 at 14:33
  • If malloc returns NULL, then you cannot pass that to free as you got no memory so neither is there a hidden control block. However, free accepts a null pointer and just ignores it. – Paul Ogilvie Jun 02 '17 at 14:34
  • 1
    The problem is you you get code like if(!allocatedptr) outofmemoryhandler() which fires if N = 0 and malloc returns NULL. It's arguably better to return a value. I just tested it and on the system installed on this computer (clang) I get a non-zero return. – Malcolm McLean Jun 02 '17 at 14:42
  • 8
    @PaulOgilvie your comment is a bit confusing; if malloc returns NULL then you CAN pass it to free – M.M Jun 02 '17 at 14:44
  • @M.M., yes you can, as I say, because it just ignores this "error" (that is, I find it an error to call free with NULL-pointer). – Paul Ogilvie Jun 02 '17 at 14:48
  • @M.M., NULL is a _non_-result (a result indicating an error or out-of-memory). – Paul Ogilvie Jun 02 '17 at 15:05
  • 4
    @PaulOgilvie-- which is why the Standard specifies ["If ptr is a null pointer, no action occurs."](http://port70.net/~nsz/c/c11/n1570.html#7.22.3.3p2) – ad absurdum Jun 02 '17 at 15:21
  • 1
    @PaulOgilvie: Like Malcolm's comment says, in practice most people assume `malloc` returning null means OOM. According to the language, this is not a correct assumption, as there is a scenario where `malloc` is permitted to return null while still having memory available. So no, it's not indicating OOM. You and many others are free to assume that, of course, but don't go around saying it's definitely so. It *would* be correct to say that `malloc` returning null on a non-zero size would indicate OOM - yes, that's a technically but welcome to language lawyer land. – GManNickG Jun 02 '17 at 22:00
  • @GManNickG It's quite abnormal to call `malloc(0)` in the first place. So while you're technically true, in practice when `malloc()` returns `NULL` it's because of OOM. – Barmar Jun 06 '17 at 18:56