arraysclanguage-lawyercalloc

What type can the result of calloc be assigned to, a pointer to an array, a pointer to the type contained within the array, or either?


According to the standard (C17 draft, 7.22.3.2), calloc

void *calloc(size_t nmemb, size_t size);

"allocates space for an array of nmemb objects, each of whose size is size" (and initializes all bits to zero).

For calloced arrays of T, I have only ever seen code like this:

T *p = calloc(nmemb, sizeof(T));
T *p;
p = calloc(nmemb, sizeof(T));

But given that calloc allocates space for an array, the following ought to be fine too:

T (*arrp)[nmemb] = calloc(nmemb, sizeof(T));
T (*arrp)[nmemb];
arrp = calloc(nmemb, sizeof(T));

(Here, the top version of each pair is technically speaking an initialization, not an assignment.)

What type can the result of calloc be assigned to, a pointer to an array (type T (*)[]), a pointer to the type contained within the array (type T *), or either?

The following code

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int (*iarrp)[5];
    int *ip;
    float (*farrp)[1];
    float *fp;
    int i;

    iarrp = calloc(5, sizeof(int));
    for (i = 0; i < 5; ++i)
        (*iarrp)[i] = -i;
    ip = calloc(5, sizeof(int));
    for (i = 0; i < 5; ++i)
        ip[i] = i + 100;
    for (i = 0; i < 5; ++i)
        printf("%d: %d, %d\n", i, (*iarrp)[i], ip[i]);

    farrp = calloc(1, sizeof(float));
    (*farrp)[0] = 5.5;
    fp = calloc(1, sizeof(float));
    *fp = 6.6;
    printf("%.2f, %.2f\n", (*farrp)[0], *fp);

    free(iarrp);
    free(ip);
    free(farrp);
    free(fp);

    return 0;
}

compiles just fine for me with GCC (gcc -std=c17 -pedantic -Wall -Wextra) and with MSVC (cl /std:c17 /Wall), with the output being as expected:

0: 0, 100
1: -1, 101
2: -2, 102
3: -3, 103
4: -4, 104
5.50, 6.60

The background for asking this question is this: For an array arr of type T[], of the following three expressions

the first two produce the same value. The third expression can theoretically have a value different from the first two expressions, though I understand that this is uncommon. The standard only guarantees that (void *)&arr == (void *)&arr[0] holds true.


Solution

  • You ask:

    What type can the result of calloc be assigned to

    The answer can be found in the standard (e.g. C17 7.22.3):

    The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object

    That's it. Any pointer type.

    Then you say:

    ... the following ought to be fine too:

    T (*arrp)[nmemb] = calloc(nmemb, sizeof(T));

    Well... yes, as long as you access the memory like (*arrp)[i] or arrp[0][i] (and 0 <= i < nmemb), it will work.

    But it's not really how calloc is intended to be used. Your code tells calloc that each element has the size sizeof(T). However, you store the return value into a pointer that points to an element with size nmemb * sizeof(T). This is because your arrp is a pointer to an array of nmemb Ts.

    For your code the intended form would be:

    T (*arrp)[nmemb] = calloc(1, nmemb * sizeof(T));
    

    A better way of writing this would be:

    T (*arrp)[nmemb] = calloc(1, sizeof *arrp);
    

    This brings to the "normal" use of this code... It's for allocation of 2 dimensional arrays (aka "arrays-of-arrays). Like this:

    T (*arrp)[num_columns] = calloc(num_rows, sizeof *arrp);
                   ^                     ^
                    \-------------------/
           notice: Unlike your example these values are not the same
    

    This code gives you a dynamic allocation that corresponds to the static/automatic allocation:

    T arr[num_rows][num_columns];
    

    BTW:

    This could be an interresting read:

    Why does calloc require two parameters and malloc just one?