Search code examples
cmultidimensional-arraydynamic-memory-allocation

What exactly is this method of contiguous C memory allocation doing under the hood?


I came across this question while looking for an effective way to allocate large multi-dimensional arrays contiguously in memory. The accepted answer suggests that for a 3D array of size sz[0] x sz[1] x sz[2] one should use this method, which is currently melting my feeble brain:

int (*a)[sz[1]][sz[2]] = calloc(sz[0], sizeof(*a));
...
free(a)

the left hand of that statement looks like a 2D array of int * allocated on the stack. The right side is a single (?!) call to calloc() that allocates int * on the heap. Since sizeof(*a)==sizeof(int *) (right?) this looks like too few allocations to make any sense, since it appears to allocate sz[0]x int * bytes, and yet it works to index over the full intended size of the array.

Can someone please help me understand how exactly this definition works to produce the intended result? Is the C compiler repeating the call to calloc for every entry in the table defined on the left? And if so, how does a single call to free() suffice to get rid of it? Does the resulting array reside entirely on the heap, or is it mixing a reference table on the stack that points to memory allocated on the heap?


Solution

  • Here is some code with a similar principle that maybe is easier to understand at first:

    typedef int THING[5][6];    // THING means a contiguous array of 5x6 ints
    
    THING arr[4];               // arr is a contiguous array of 4 THINGs
    THING *first = &arr[0];     // The expression *first would yield the first thing.
    

    Hopefully you recognize the last two lines here as being common syntax for non-dynamic allocation of any array, and referring to the array's first element. That works just the same whether or not THING is itself an array.

    Now, &arr[0] points to a memory location that is the start of a contiguous block of ints of size 4x5x6. if you use dynamic allocation to make that block it looks like:

    THING *first = malloc( sizeof(int[4][5][6]) );
    

    If we expand out the typedef in this last line it looks like:

    int (*first)[5][6] = malloc( sizeof(int[4][5][6]) );
    

    The code in your question is the same as this last line , except that:

    • it uses variables instead of hardcoded integers (which is allowed since C99).
    • it uses calloc instead of malloc.
    • it uses a more robust syntax for calculating the size to allocate, see here for explanation.