calloc的结果可以赋给什么类型,是指向数组的指针,还是指向数组中包含的类型的指针,或者两者之一?

pod7payv  于 2023-10-16  发布在  其他
关注(0)|答案(2)|浏览(129)

根据标准(C17草案,7.22.3.2),calloc
void *calloc(size_t nmemb, size_t size);
“为nmemb对象的数组分配空间,每个对象的大小为size“(并将所有位都设置为零)。
对于Tcalloc艾德数组,我只见过这样的代码:

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

但是考虑到calloc为一个 array 分配空间,下面的代码也应该没问题:

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

(Here,从技术上讲,每对的最高版本是一个 * 初始化 *,而不是一个 * 赋值 *。

calloc的结果可以赋给什么类型,一个指向数组的指针(类型T (*)[]),一个指向数组中包含的类型的指针(类型T *),或者两者之一?

下面的代码

#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;
}

对于我来说,使用GCC(gcc -std=c17 -pedantic -Wall -Wextra)和MSVC(cl /std:c17 /Wall)编译得很好,输出与预期的一样:

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

提出这个问题的背景是这样的:对于T[]类型的数组arr,以下三个表达式中的

  • arr;类型:T[](衰减前),T *(衰减后)
  • &arr[0];类型:T *
  • 这就是arr衰减到
  • &arr;类型:T (*)[]
  • &防止衰变

前两个产生相同的值。理论上,第三个表达式的值可以不同于前两个表达式,尽管我知道这是不常见的。标准只保证(void *)&arr == (void *)&arr[0]为真。

ajsxfq5m

ajsxfq5m1#

calloc返回的动态分配内存没有 * 有效类型 *,根据C 2018 6.5 6。在C语义中,它只是一个可以用于任何对象类型的内存区域。
它可以通过使用非字符类型向其中存储值来获取有效类型。(并且这个有效类型可以通过将来的分配进行更改-内存可以重新用于其他类型。
C标准不清楚具有聚合的有效类型的精确形式语义。数组的元素和结构的成员可以单独存储在存储器中,而不存储整个聚合。尽管如此,很明显,我们可以使用动态分配的内存来存储聚合,或者一次全部(对于结构),或者与单个元素或成员一起存储。
是否应该使用T *T (*)[]类型的指针访问内存的问题没有任何后果。给定声明T *p0,一个值被存储到*p0 = value;的内存中。在这个赋值中,*被应用到存储在p0中的地址,以形成分配内存中T元素的左值。给定声明T (*p1)[nmemb],一个值被存储到(*p1)[i] = value;的内存中。在这个赋值中,*被应用于存储在p1中的地址,以形成数组的左值,这个左值被转换为指向数组第一个元素的指针,然后下标运算符被应用于形成数组元素i的左值。因此,在任何一种情况下,用于将值实际存储到内存中的左值都是元素的左值。T *T (*)[]的选择只影响左值的中间计算,而不是用于存储的最终左值。
这个问题对结构更有意思。给定typedef struct { int i, j; } T; T *p = calloc(1, sizeof *p);,我们可以用*p = (T) { 7, 13 };来给整个结构赋值,或者用p->i = 7;来给一个成员赋值。如果我们做后者,我们是否将存储器的有效类型设置为T?或者我们只将部分内存的有效类型设置为int?C标准没有充分说明这一点。

9udxz4iz

9udxz4iz2#

你问:
calloc的结果可以赋给什么类型
答案可以在标准中找到(例如,C17 7.22.3):
如果分配成功,则返回的指针适当对齐,以便它可以分配给指向任何类型对象的指针
就这样。任何指针类型。
然后你说:
以下内容也应该不错:
T (*arrp)[nmemb] = calloc(nmemb, sizeof(T));
嗯...是的,只要你访问像(*arrp)[i]arrp[0][i](和0 <= i < nmemb)这样的内存,它就能工作。
但这并不是calloc的真正用途。代码告诉calloc每个元素的大小为sizeof(T)。但是,您将返回值存储到指向大小为nmemb * sizeof(T)的元素的指针中。这是因为arrp是一个指向nmemb Ts数组的指针。
对于您的代码,预期的格式将是:

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

更好的写法是:

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

这就导致了这个代码的“正常”使用。它用于分配2维数组(又名“数组的数组”)。就像这样:

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

这段代码给你一个动态分配,对应于静态/自动分配:

T arr[num_rows][num_columns];

顺便说一句:
这可能是一个有趣的阅读:
Why does calloc require two parameters and malloc just one?

相关问题