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?

124 Views Asked by At

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

  • arr; type: T[] (before decay), T * (after decay)
  • &arr[0]; type: T *
    • this is what arr decays to
  • &arr; type: T (*)[]
    • the & prevents decay

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.

2

There are 2 best solutions below

5
Support Ukraine On BEST ANSWER

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?

0
Eric Postpischil On

The dynamically allocated memory returned by calloc has no effective type, per C 2018 6.5 6. In C semantics, it is just a region of memory that can be used for any object type.

It can acquire an effective type by storing a value into it using a non-character type. (And this effective type can be changed by future assignments—the memory can be reused for other types.)

The C standard is unclear about the precise formal semantics of effective types with aggregates. Elements of arrays and members of structures may be stored individually in memory without storing the entire aggregate. Nonetheless, it is clear that we may use dynamically allocated memory to store aggregates, either all at once (for structures) or with individual elements or members.

The issue of whether the memory “should” be accessed using a pointer of type T * or T (*)[] has no consequence. Given declaration T *p0, a value is stored to the memory with *p0 = value;. In this assignment, the * is applied to the address stored in p0 to form an lvalue for a T element in the allocated memory. Given declaration T (*p1)[nmemb], a value is stored to the memory with (*p1)[i] = value;. In this assignment, the * is applied to the address stored in p1 to form an lvalue for the array and this lvalue is converted to a pointer to the first element of the array, and then the subscript operator is applied to form an lvalue for the element i of the array. So, in either case, the lvalue used for the actual store of a value into the memory is an lvalue for the element. The choice of T * or T (*)[] only affects intermediate calculations of the lvalue, not the final lvalue used for the store.

The question is more interesting for structures. Given typedef struct { int i, j; } T; T *p = calloc(1, sizeof *p);, we could do *p = (T) { 7, 13 }; to assign a value to the whole structure or p->i = 7; to assign a value to just one member. If we do the latter, have we set the effective type of the memory to T? Or have we only set the effective type of part of the memory to int? The C standard does not specify this adequately.