3

I am working to develop debug implementations of the four basic memory allocation routines malloc, realloc, calloc, and free (similar in operation to Electric Fence) to debug heap corruption on embedded systems which do not have the resources to run other memory debugging tools, or for which other tools do not exist (for instance, LynxOS 7.0 for PowerPC ships with GCC 4.6.3, and a proprietary implementation of glibc and libstdc++ which does not include the mtrace family of functions).

The following is the source code for calloc, from calloc.c in GCC's libiberty.

PTR
calloc (size_t nelem, size_t elsize)
{
  register PTR ptr;  

  if (nelem == 0 || elsize == 0)
    nelem = elsize = 1;

  ptr = malloc (nelem * elsize);
  if (ptr) bzero (ptr, nelem * elsize);

  return ptr;
}

Why are nelem and elsize both set equal to 1 if either equals 0?

If I attempt to allocate 0 chunks of size n or n chunks with 0 size, wouldn't either case result in an aggregate allocation of 0 total bytes, and not 1 byte?

Barmar
  • 741,623
  • 53
  • 500
  • 612
Vinny
  • 154
  • 5
  • 2
    `malloc` and `calloc` are required by specification to return unique values. – Kerrek SB Oct 18 '19 at 00:31
  • @KerrekSB But since `malloc()` already has this requirement, `calloc()` doesn't need its own check. – Barmar Oct 18 '19 at 00:35
  • @Barmar I thought about your idea that "since malloc() already has this requirement, calloc() doesn't need its own check.". A functional difference here to the redundant check is that by doing it here, the 1 byte allocation is zero'd. Without `if (nelem == 0 || elsize == 0) nelem = elsize = 1;`, the allocation remains as is. – chux - Reinstate Monica Oct 18 '19 at 16:27
  • @chux That's true, but it's undefined behavior to access the memory, so it shouldn't be necessary. – Barmar Oct 18 '19 at 16:46
  • @Barmar Agree about UB, but this is some sort of coding paradigm called _Electric Fence_ and deterministic behavior may be desired. – chux - Reinstate Monica Oct 18 '19 at 16:50

2 Answers2

1

Yes, this is just bad code, which is not unexpected from libiberty/gnulib/etc. As I understand it they already replace malloc if malloc(0) returns a null pointer rather than a unique pointer for each call, so I don't see any good reason for making calloc pass 1 to malloc instead of 0. Moreover, passing 1 breaks/undermines debugging tools like sanitizers that could tell you if you inadvertently dereferenced the pointer to a "zero-element array".

The code is also incredibly dangerous in that it does not check for overflow of the multiplication; any correct implementation of calloc is required to do this.

TL;DR: this code is junk.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
1

Why does calloc allocate 1 byte if nelem or elsize == zero?

To reduce ambiguity.

calloc(0, x), calloc(x, 0), malloc(0), when successful, may return NULL or a non-NULL pointer. In both cases, the pointer may not be de-referenced without causing undefined behavior (UB).

When unsuccessful, a NULL pointer is returned.

By insuring the allocation size is more than 0, there is no ambiguity when NULL is returned - the allocation failed from this calloc().


Note, a better function would also detect product overflow.

if (nelem == 0 || elsize == 0) {
  nelem = elsize = 1;
} else if (SIZE_MAX/nelem > elsize) {
  return NULL;  // Too much for this implementation
}
...

Further thought about Electric_Fence:

I'd expect the malloc(0) of that project to also insure that 0 bytes are not allocated. Yet simply calling ptr = malloc (0); if (ptr) bzero (ptr, 0); would not zero that 1 byte allocation. if (nelem == 0 || elsize == 0) nelem = elsize = 1; makes certain the allocation is at least 1 byte and that allocation is zeroed.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256