Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can calloc() allocate more than SIZE_MAX in total?

In a recent code review, it was claimed that

On select systems, calloc() can allocate more than SIZE_MAX total bytes whereas malloc() is limited.

My claim is that that's mistaken, because calloc() creates space for an array of objects - which, being an array, is itself an object. And no object can be larger in size than SIZE_MAX.

So which of us is correct? On a (possibly hypothetical) system with address space larger than the range of size_t, is calloc() allowed to succeed when called with arguments whose product is greater than SIZE_MAX?

To make it more concrete: will the following program ever exit with a non-zero status?

#include <stdint.h> #include <stdlib.h>  int main() {      return calloc(SIZE_MAX, 2) != NULL; } 
like image 649
Toby Speight Avatar asked Oct 08 '18 09:10

Toby Speight


2 Answers

Can calloc() allocate more than SIZE_MAX in total?

As the assertion "On select systems, calloc() can allocate more than SIZE_MAX total bytes whereas malloc() is limited." came from a comment I posted, I will explain my rationale.


size_t

size_t is some unsigned type of at least 16 bits.

size_t which is the unsigned integer type of the result of the sizeof operator; C11dr §7.19 2

"Its implementation-defined value shall be equal to or greater in magnitude ... than the corresponding value given below" ... limit of size_t SIZE_MAX ... 65535 §7.20.3 2

sizeof

The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. §6.5.3.4 2

calloc

void *calloc(size_t nmemb, size_t size); 

The calloc function allocates space for an array of nmemb objects, each of whose size is size. §7.22.3.2 2


Consider a situation where nmemb * size well exceeds SIZE_MAX.

size_t alot = SIZE_MAX/2; double *p = calloc(alot, sizeof *p); // assume `double` is 8 bytes. 

If calloc() truly allocated nmemb * size bytes and if p != NULL is true, what spec did this violate?

The size of each element, (each object) is representable.

// Nicely reports the size of a pointer and an element. printf("sizeof p:%zu, sizeof *p:%zu\n", sizeof p, sizeof *p);  

Each element can be accessed.

// Nicely reports the value of an `element` and the address of the element for (size_t i = 0; i<alot; i++) {   printf("value a[%zu]:%g, address:%p\n", i, p[i], (void*) &p[i]);  } 

calloc() details

"space for an array of nmemb objects": This is certainly a key point of contention. Does the "allocates space for the array" require <= SIZE_MAX? I found nothing in the C spec to require this limit and so conclude:

calloc() may allocate more than SIZE_MAX in total.


It is certainly uncommon for calloc() with large arguments to return non-NULL - compliant or not. Usually such allocations exceed memory available, so the issue is moot. The only case I've encountered was with the Huge memory model where size_t was 16 bit and the object pointer was 32 bit.

like image 91
chux - Reinstate Monica Avatar answered Sep 22 '22 20:09

chux - Reinstate Monica


SIZE_MAX doesn't necessary specify the maximum size of an object, but rather the maximum value of size_t, which is not necessarily the same thing. See Why is the maximum size of an array "too large"?,

But obviously, it isn't well-defined to pass a larger value than SIZE_MAX to a function expecting a size_t parameter. So in theory SIZE_MAX is the limit, and in in theory calloc would allow for SIZE_MAX * SIZE_MAX bytes to allocated.

The thing with malloc/calloc is that they allocate objects without a type. Objects with a type have restrictions, such as never being larger than a certain limit like SIZE_MAX. But the data pointed-at by the result from these functions does not have a type. It is not (yet) an array.

Formally, the data has no declared type, but as you store something inside the allocated data, it gets the effective type of the data access used for storage (C17 6.5 §6).

This in turn means that it would be possible for calloc to allocate more memory than any type in C can hold, because what's allocated does not (yet) have a type.

Therefore, as far as the C standard is concerned, it is perfectly fine for calloc(SIZE_MAX, 2) to return a value different from NULL. How to actually use that allocated memory in a sensible way, or which systems that even support such large chunks of memory on the heap, is another story.

like image 27
Lundin Avatar answered Sep 21 '22 20:09

Lundin