Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

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.

Comments on Why does calloc accept 2 arguments, and with what arguments should one call it?

Parent

Why does calloc accept 2 arguments, and with what arguments should one call it?

+5
−0

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 and size would result in integer overflow, then calloc() returns an error. By contrast, an integer overflow would not be detected in the following call to malloc(), 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?

History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

1 comment thread

"Also heard" where, and what exactly? (4 comments)
Post
+4
−0

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 of malloc/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 is size_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 typing int 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 by calloc's own bad API is taking place.

History
Why does this post require moderator attention?
You might want to add some details to your flag.

2 comment threads

Patches to the manual page are welcome (2 comments)
Having 2 arguments can avoid wrap-around bugs. (1 comment)
Having 2 arguments can avoid wrap-around bugs.
alx‭ wrote 7 months ago · edited 7 months ago
size_t  n, size;
int *p;

n = MIN(PTRDIFF_MAX, SIZE_MAX) - 1;  // A huge valid number of elements.
//size = sizeof(int[n]);  // Is this even legal?
size = n * sizeof(int);  // This wraps around, producing a bogus size.
p = malloc(size);  // No UB, but...
if (p)
        exit(1);
p[n - 1] = 0;  // Here we have UB.

This is a bug in the caller, not in malloc(3), but one may argue that the malloc(3) API is bug-prone in this case. calloc(3) allows one to do the following

p = calloc(n, sizeof(int));

simplifying the caller, and removing one opportunity for bugs.