[HN Gopher] Some bits on malloc(0) in C being allowed to return ...
___________________________________________________________________
Some bits on malloc(0) in C being allowed to return NULL
Author : ingve
Score : 43 points
Date : 2025-06-25 06:15 UTC (1 days ago)
(HTM) web link (utcc.utoronto.ca)
(TXT) w3m dump (utcc.utoronto.ca)
| bobmcnamara wrote:
| Ages ago I worked with a system where malloc(0) incremented a
| counter and returned -1.
|
| free(-1) decremented the counter.
|
| This way you could check for leaks :p
| o11c wrote:
| Noncompliant, since `malloc(0)` is specified to return a
| _unique_ pointer if it 's not `NULL`.
|
| On most platforms an implementation could just return adjacent
| addresses from the top half of the address space. On 32-bit
| platforms it doesn't take long to run out of such address space
| however, and you don't want to waste the space for a bitmap
| allocator. I suppose you _could_ just use a _counter_ for each
| 64K region or something, so you can reuse it if the right
| number of elements has been freed ...
| LPisGood wrote:
| Noncompliant, but what could this reasonably impact?
| bobmcnamara wrote:
| > Noncompliant, since `malloc(0)` is specified to return a
| unique pointer if it's not `NULL`.
|
| I know I've seen that somewhere, but may I ask what
| standard you're referring to?
| masfuerte wrote:
| It's POSIX.
|
| > Each [...] allocation shall yield a pointer to an
| object disjoint from any other object. The pointer
| returned points to the start (lowest byte address) of the
| allocated space. If the space cannot be allocated, a null
| pointer shall be returned. If the size of the space
| requested is 0, the behavior is implementation-defined:
| either a null pointer shall be returned, or the behavior
| shall be as if the size were some non-zero value, except
| that the behavior is undefined if the returned pointer is
| used to access an object.
|
| https://pubs.opengroup.org/onlinepubs/9799919799/function
| s/m...
| MaxBarraclough wrote:
| Not just POSIX, also the ISO C standard itself.
| https://en.cppreference.com/w/c/memory/malloc
| masfuerte wrote:
| That doesn't say the pointer has to be unique.
| jcranmer wrote:
| cppreference isn't the standard, and while the text they
| write looks like it's the same verbiage that would be
| authoritative, it's not. (And there's some criticism of
| it from standards committee members in that regard).
|
| The current C standard text says:
|
| > The order and contiguity of storage allocated by
| successive calls to the aligned_alloc, calloc, malloc,
| and realloc functions is unspecified. The pointer
| returned if the allocation succeeds is suitably aligned
| so that it can be assigned to a pointer to any type of
| object with a fundamental alignment requirement and size
| less than or equal to the size requested. It can then be
| used to access such an object or an array of such objects
| in the space allocated (until the space is explicitly
| deallocated). The lifetime of an allocated object extends
| from the allocation until the deallocation. Each such
| allocation shall yield a pointer to an object disjoint
| from any other object. 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 to indicate an error, 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.
|
| So yeah, the allocations are required to be unique (at
| least until it's free'd).
| bobmcnamara wrote:
| > Each such allocation shall yield a pointer to an object
| disjoint from any other object.
|
| Phrasing could be slightly more clear to prevent someone
| from making the argument that -1 is disjoint from all
| objects as it does not point to an object
| bobmcnamara wrote:
| It is in ANSI 89, under memory management functions.
| o11c wrote:
| Pointers are frequently used as keys for map-like data
| structures. This introduces collisions that the programmer
| can't check for, whereas NULL is very often special-cased.
| bobmcnamara wrote:
| > Noncompliant, since `malloc(0)` is specified to return a
| unique pointer if it's not `NULL`.
|
| I know I've seen that somewhere, but may I ask what standard
| you're referring to?
|
| If I recall correctly, this was an archaic stackless
| microcontroller. The heap support was mostly a marketing
| claim.
| jmgao wrote:
| C89: https://port70.net/%7Ensz/c/c89/c89-draft.html
|
| If the size of the space requested is zero, the behavior is
| implementation-defined; the value returned shall be either
| a null pointer or a unique pointer.
| f1shy wrote:
| Isn't -1 basically 0xffff which is a constant pointer?
| What am I missinterpreting?
| comex wrote:
| If you call malloc(0) multiple times (without freeing in
| between) and get -1 each time, then the pointer is not
| unique.
| bobmcnamara wrote:
| But do we need a unique pointer or merely a pointer that
| is disjoint from all objects?
| fredoralive wrote:
| Presumably the ANSI C standard or one of the later
| editions? They also cover the standard library as well as
| the language. (Presumably the bit about "Each such
| allocation shall yield a pointer to an object disjoint from
| any other object." if the random C99 draft I found via
| google is accurate to the final standard - I suppose you
| might question if this special use is technically an
| allocation of course).
|
| Of course, microcontrollers and the like can have somewhat
| eccentric implementations of languages of thing and perhaps
| aren't strictly compliant, and frankly even standard
| compliant stuff like "int can be 16 bits" might surprise
| some code that doesn't expect it.
| o11c wrote:
| (you duped your comment under the other subthread)
|
| From C89, SS7.10.3 "Memory management functions":
|
| > If the size of the space requested is > zero, the
| behavior is implementation-defined; the value returned
| shall be either a null pointer or a > unique pointer.
|
| The wording is different for C99 and POSIX, but I went back
| as far as possible (despite the poor source material;
| unlike later standards C89 is only accessible in scans and
| bad OCR, and also has catastrophic numbering differences).
| K&R C specifies nothing (it's often quite useless; people
| didn't actually write against K&R C but against the common
| subset of extensions of platforms they cared about), but
| its example implementation adds a block header without
| checking for 0 so it ends up doing the "unique non-NULL
| pointer" thing.
| sgerenser wrote:
| I might be missing something, but how does this help in
| checking for leaks? I mean, I guess you could use it to check
| for leaks specifically of 0-sized allocations, but wouldn't it
| be better just to return NULL and guarantee that 0-sized
| allocations never use any memory at all?
| bobmcnamara wrote:
| At the end of main, if the count wasn't balanced, then you
| knew you had a mismatch between malloc()/free().
|
| If malloc() had returned a real pointer, you'd have to free
| that too.
|
| > wouldn't it be better just to return NULL and guarantee
| that 0-sized allocations never use any memory at all?
|
| Better: takes less memory Worse: blinds you to this
| portability issue.
| Someone wrote:
| > At the end of main, if the count wasn't balanced, then
| you knew you had a mismatch between malloc()/free().
|
| A mismatch between _malloc(0)_ and _free(-1)_.
|
| You'd know nothing about calls to _malloc_ with non-zero
| sizes.
| sgerenser wrote:
| Yeah, exactly, that's my point. How many programs have
| memory leaks limited to (or even just materially affected
| by) 0-sized allocations? I'd have to imagine its a very
| small minority.
| spacechild1 wrote:
| > but wouldn't it be better just to return NULL and guarantee
| that 0-sized allocations never use any memory at all?
|
| This works if you are only interested in the overall memory
| balance. However, if you want to make sure that all malloc()
| calls are matched by a free() call, you need to distinguish
| between NULL and a successfull zero-sized allocation,
| otherwise you run into troubles when you call free on an
| "actual" NULL pointer (which the standard defines as a no-
| op).
| sweetjuly wrote:
| Does this work in practice? Now you have a bunch of invalid but
| non-NULL pointers flying around. NULL checks which would
| normally prevent you from accessing invalid pointers now will
| pass and send you along to deref your bogus pointer.
|
| Even hacking the compiler to treat -1 as equal to NULL as well
| wouldn't work since lots of software won't free NULL-like
| pointers.
| AaronDinesh wrote:
| Why should it be allowed to return a valid pointers anyways?
| Surely it should always return NULL?
| Joker_vD wrote:
| For instance, because you are prohibited from passing NULL to
| e.g. memcpy and lots of other library functions from
| memory.h/string.h, even when you explicitly specify a size of
| 0.
|
| Another use was to use it to mint unique cookies/addresses, but
| malloc(1) works for this just as well.
| TZubiri wrote:
| Mmmmh, cookies
| snickerbockers wrote:
| It's not a valid pointer because you can't use the indirection
| operator on it. Returning a value other than NULL makes sense
| because an allocation of size zero is still an allocation.
|
| Additionally the actual amount of memory malloc allocates is
| implementation-defined so long as it is not less than the
| amount requested, but accessing this extra memory is undefined
| behavior since processes don't know if it exists or not. a non-
| NULL return could be interpreted as malloc(0) allocating more
| than zero bytes.
|
| Some implementations don't actually perform the allocation
| until theres a pagefault from the process writing to or reading
| from that memory so in that sense a non-NULL return is valid
| too.
|
| I'd argue that malloc(0)==NULL makes less sense because there's
| no distinction between failure and success.
|
| The only real problem is specifying two alternate behaviors and
| declaring them both to be equally valid.
| cjensen wrote:
| There are three reasonable choices: (a) return the null pointer
| (b) return a valid unique pointer and (c) abort().
|
| The point of the original C Standard was to make rules about
| these things AND not break existing implementations. They
| recognized that (a) and (b) were in existing implementations
| and were reasonable, and they chose not to break the existing
| implementations when writing the standard.
|
| This is similar to the extremely unfortunate definition of the
| NULL macro. There were two existing styles of implementation
| (bare literal 0 and (void *) 0) and the Standard allows either
| style. Which means the NULL macro is not entirely safe to use
| in portable code.
| commandlinefan wrote:
| > return a valid unique pointer
|
| A pointer to what, though? If the requester asked for 0 bytes
| of memory, you'd either be pointing to memory allocated for
| another purpose (!) or allocating a few bytes that weren't
| asked for.
|
| > This makes people unhappy for various reasons
|
| I read through all the links trying to figure out what those
| reasons might be and came up empty, I'm still curious why
| anybody would expect or rely on anything except a null
| pointer in this instance.
| tedunangst wrote:
| You can copy from a zero sized pointer with memcpy, but not
| NULL.
| DSMan195276 wrote:
| > allocating a few bytes that weren't asked for.
|
| FWIW the alignment guarantees of `malloc()` mean it often
| will have to allocate more than you ask for (before C23
| anyway). You can't 'legally' use this space, but `malloc()`
| also can't repurpose it for other allocations because it's
| not suitably aligned.
|
| That said I still agree it's a hack compared to just using
| `malloc(1)` for this purpose, it's well-defined and
| functionally equivalent if you're looking for a unique
| address. The fact that you don't know what `malloc(0)` is
| going to do makes it pretty useless anyway.
| Joker_vD wrote:
| > before C23 anyway
|
| Did they change "suitably aligned for any object type" to
| "suitably aligned for any object type with size less than
| or equal to what was requested" or something like in C23?
| JdeBP wrote:
| See https://news.ycombinator.com/item?id=44390258 .
| AaronAPU wrote:
| The only requirement which seems reasonable to me, is that
| the address be unique. Since the allocation size is zero,
| it should never be accessed for read or write, but the
| address itself may need to be used for comparisons.
|
| If you're pointing to a zero sized data it shouldn't matter
| what it's pointing to. Even outside valid address space.
| Because you shouldn't be reading or writing more than 0
| bytes anyway.
| spacechild1 wrote:
| > or allocating a few bytes that weren't asked for.
|
| You are always allocating bytes you weren't asked for: the
| allocation metadata and some extra bytes to satisfy the
| alignment requirement. If you absolutely don't want to
| allocate memory, you probably shouldn't have called
| malloc() in the first place :)
| mcherm wrote:
| The behavior of malloc(x) for any positive value x is to either
| return NULL (meaning that the system was unable to provide a
| new chunk of memory to use) OR to return a unique pointer to X
| bytes of data which the program can use.
|
| By extension, if x == 0, doesn't it make sense for the system
| to either return NULL OR to return a pointer to 0 bytes of
| memory which the program can use? So the standard promises
| exactly that: to return either NULL or else a unique pointer
| where that the program has permission to use zero bytes
| starting at that pointer.
| carra wrote:
| Not the best choice to begin the title with "some bits" in this
| context. My mind was trying to understand this sentence in a
| completely different way...
| eesmith wrote:
| I maintained a program which failed on, as I recall, AIX
| (mentioned in the essay) because malloc(0) returned NULL.
|
| It's been 30 years so I've forgotten the details. My solution was
| to always allocate size+1 since memory use was far from critical.
| randomNumber7 wrote:
| I never had the use case to allocate 0 bytes of memory.
|
| If I would allocate 0 bytes of memory and get a pointer to it, I
| wouldn't care what the value of the pointer is since I am not
| allowed to dereference it anyways.
|
| But then again, why would I allocate 0 bytes of memory?
| Lvl999Noob wrote:
| Can someone tell me a usecase where you want multiple allocations
| of size 0, each one with a unique address, and each one unique
| from any other allocation (hence necessarily removing that
| pointer from being allocated to anything else) but can't use
| malloc(1) instead?
|
| I think it would be much better if malloc(0) just returned 1 or
| -1 or something constant. If the programmer needs the allocation
| to have a unique address, they can call malloc(1) instead.
| hansvm wrote:
| It's occasionally useful to want multiple allocations of size
| 0, each one with a valid address -- generic containers parsing
| something as a some sort of sequence object and you want all
| code interacting with it to do something valid. I'd be hard-
| pressed to see where you'd need those to be unique though.
| Basically any integer should be fine.
| xenadu02 wrote:
| Because zero-size types exist which you might want to take the
| address of. Possibly as a result of macro substitution or
| templating mechanism that only appears in certain build
| configurations.
|
| It means you don't need a bunch of special-case handling if one
| out of 27 types ends up with zero size in some situation. It
| just all works the same way. Especially the unique address part
| because that would be an annoying source of difficult to track
| bugs.
| tptacek wrote:
| I get the complexity of the standards issue here, but if you
| cared about this, wouldn't you just wrap malloc with something
| trivial that provided the semantic you wanted to depend on (NULL
| or some sentinel pointer).
| a-dub wrote:
| would be interesting to see if there's a difference in how the
| 0-page is handled in systems under this condition...
___________________________________________________________________
(page generated 2025-06-26 23:02 UTC)