Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Why does calloc accept 2 arguments, and with what arguments should one call it?
According to the standard (C17 draft, 7.22.3.2) The function calloc
void *calloc(size_t nmemb, size_t size);
"allocates space for an array of nmemb objects, each of whose size is size [and] initialize[s] [...] all bits [to] zero". Like malloc, it returns a void * pointer to the allocated space or a null pointer on failure.
Unlike malloc
void *malloc(size_t nbytes);
calloc takes two arguments. I read that the function signature of calloc lets a good implementation check for some sort of multiplicative overflow. For example, this manpage states (formatting adapted):
If the multiplication of
nmembandsizewould result in integer overflow, thencalloc()returns an error. By contrast, an integer overflow would not be detected in the following call tomalloc(), with the result that an incorrectly sized block of memory would be allocated:
malloc(nmemb * size);
But I also heard that its 2-argument function signature is flawed and that the following calls are equivalent:
calloc(1, m*n)
calloc(m, n)
calloc(n, m)
calloc(m*n, 1)
(The last example was added by myself.)
This leads me to ask: Why does calloc accept 2 arguments, and with what arguments should one call it? Is its function signature designed well?
1 answer
It has 2 parameters for weird historical reasons that nobody seems to know the rationale for any longer. Like most functions in the C standard library, the function API was not well-designed. Keep in mind that many of these functions were designed in the 1960s(!) and early 1970s, some 20 years before good programming practices were invented. By the time C got ISO standardized, they chose to pick existing functions at a whim rather than encouraging better functions to be designed.
I just checked K&R 1st edition and it has two functions alloc and calloc, with a corresponding cfree for calloc specifically. calloc has 2 parameters even there and K&R 1st edition recommends a cast of the result (I believe void* had not yet been invented).
The C99 rationale document points out that calloc(0, sizeof(OBJ)); is apparently wide-spread use (according to the rationale), supposedly as a way to distinguish between "nothing allocated" and "zero bytes allocated". The Committee did not recommend it - this is implementation-defined and non-portable (and soon to be explicitly undefined behavior in C23).
That man page is confused and has some technical problems:
-
It is not possible for the unsigned
size_targument ofmalloc/callocto overflow, although wrap-around is possible. These two terms have very different meaning in C. Overflow means signed overflow, which is undefined behavior. Wrap-around means an unsigned integer going beyond the max value and starting over at zero, well-defined and portable. -
Similarly due to "the usual arithemtic promotions",
nmemb * sizecannot overflow, given that at least one parameter issize_tor another large unsigned type. It can only wrap-around.In case neither operand is a large unsigned integer type, then that's a caller-side arithmetic bug completely unrelated to the
mallocfunction. Like for example the common beginner mistake of typingintall over the place. -
malloccan at most allocate 2(CHAR_BIT * sizeof(size_t)) - 1 bytes, likely 232-1 or 264-1 on mainstream systems.callocopens up the possibility to request even more than that through its dysfunctional 2-parameter API. -
So the only "increased" error checking
calloccan do internally is to ensure that no over-allocation caused bycalloc's own bad API is taking place.

1 comment thread