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
nmemb
andsize
would 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_t
argument ofmalloc
/calloc
to 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 * size
cannot overflow, given that at least one parameter issize_t
or 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
malloc
function. Like for example the common beginner mistake of typingint
all over the place. -
malloc
can at most allocate 2(CHAR_BIT * sizeof(size_t)) - 1 bytes, likely 232-1 or 264-1 on mainstream systems.calloc
opens up the possibility to request even more than that through its dysfunctional 2-parameter API. -
So the only "increased" error checking
calloc
can do internally is to ensure that no over-allocation caused bycalloc
's own bad API is taking place.
1 comment thread