我对 C 上的 malloc() 和 calloc() 感到非常困惑

Posted

技术标签:

【中文标题】我对 C 上的 malloc() 和 calloc() 感到非常困惑【英文标题】:I'm very confused about malloc() and calloc() on C 【发布时间】:2011-05-13 09:07:57 【问题描述】:

我一直用 Java 编程,这可能是我对此感到如此困惑的原因:

在 Java 中我声明了一个指针:

int[] array

并对其进行初始化或分配一些内存:

int[] array = 0,1,0
int[] array = new int[3]

现在,在 C 中,一切都变得如此混乱。一开始我以为就这么简单:

int array[]

并对其进行初始化或分配一些内存:

int array[] = 0,1,0
int array[] = malloc(3*sizeof(int))
int array[] = calloc(3,sizeof(int))

除非我错了,以上都是等价的Java-C,对吧?

然后,今天我遇到了一个代码,其中我发现了以下内容:

pthread_t tid[MAX_OPS];

以及下面的一些行,没有任何初始化......

pthread_create(&tid[0],NULL,mou_usuari,(void *) 0);

令人惊讶的是(至少对我而言),代码有效!至少在 Java 中,这会返回一个不错的“NullPointerException”!

所以,按顺序:

    我对所有 Java-C“翻译”都正确吗?

    为什么该代码有效?

    使用malloc(n*sizeof(int))calloc(n,sizeof(int))有什么区别吗?

提前致谢

【问题讨论】:

【参考方案1】:

您不能将内存分配给数组。数组在其整个生命周期内具有固定的大小。数组永远不能为空。数组不是指针。

malloc 将地址返回到为程序保留的内存块。您不能将其(作为内存块)“分配”给数组,但您可以将此内存块的地址存储在指针中:幸运的是,数组订阅是通过指针定义的 - 因此您可以“使用像数组一样的指针” ,例如

int *ptr = malloc(5 * sizeof *ptr);
ptr[2] = 5; // access the third element "of ptr"
free(ptr); // always free at the end

当你声明一个没有大小的数组时(即array[]),它仅仅意味着数组的大小是从初始化列表中确定的。那是

int array[] = 1, 2, 3, 4, 5; // is equal to
int array[5] = 1, 2, 3, 4, 5;

试图声明一个没有大小且没有初始化器的数组是错误的。


代码pthread_t tid[MAX_OPS]; 声明了一个名为tid 的数组,类型为pthread_t,大小为MAX_OPS

如果数组有自动存储(即声明是在函数内部,而不是静态的,不是全局的),那么每个数组元素都有不确定的值(它会导致试图读取这样的值的未定义行为)。幸运的是,函数调用所做的只是将数组的第一个元素的地址作为第一个参数,并可能在函数内部初始化它(元素)。


callocmalloc的区别在于calloc返回的内存块被初始化为零。那就是;

int *ptr = calloc(5, sizeof *ptr);
// is somewhat equal to
int *ptr = malloc(5 * sizeof *ptr);
memset(ptr, 0, 5 * sizeof *ptr);

两者的区别

int *ptr = malloc(5 * sizeof *ptr);
// and
int array[5];

array 具有自动存储功能,(存储在堆栈上),并且在超出范围后被“释放”。但是,ptr(存储在堆上)是动态分配的,程序员必须是freed。

【讨论】:

第一段有一些危险的模棱两可的断言。 OP 没有尝试将内存分配给数组,他试图分配一个 (void *),从 malloc() 返回到一个数组,如果该数组是一个 int *Array[i],可能在一个for 循环,它可以正常工作,并且是如何在堆外分配动态多维数组的基础。此外,C99 支持在堆栈外分配可变大小的数组,这是少数 C 程序员使用的功能,最喜欢 alloca(),包括我自己。 ***.com/q/1018853/2548100 calloc() 几乎就是 memset(malloc(n * mysize),0, (n * mysize))。特别是因为 C 使用以空字符结尾的字符串,所以 calloc() 非常有用,尤其是在调试器中查看字符串时,通常只显示空字符结尾的字符串。如果您只是用 C 来说明,请使用 calloc 而不是 malloc,它将使您免于产生许多可能并且可能会使您的程序崩溃的未终止的 C 字符串错误。对于生产/发布代码,仅当您实际需要将缓冲区/数组/向量初始化为 (_int8) 0 时才使用 calloc()。 只是为了总结,为了完整起见,数组是一个指针。事实上,C 中的任何数组名称都准确地是指向数组中第一个对象的第一个字节的基址的指针,仅此而已。对于来自 Java、.Net 等的人来说,知道 C 将对象/变量的类型与分配用于保存它们的存储完全分开是很有帮助的。这就是为什么您可以将指针转换为 int、创建 UNION 等的原因。非常非常灵活,但对于新手来说很危险。当您分配一个 int 数组时,它只是存储在一个位置。你可以把任何你喜欢的东西放在那个存储空间里。【参考方案2】:

您遗漏了三个非常基本且严格(且具有误导性!)的 C 主题:

数组和指针的区别 静态分配和动态分配的区别 与在堆栈或堆上声明变量的区别

如果你写int array[] = malloc(3*sizeof(int));,你会得到一个编译错误(类似于'identifier':数组初始化需要花括号)。

这意味着声明一个数组只允许静态初始化:

int array[] = 1,2,3; 在堆栈上保留 3 个连续整数; int array[3] = 1,2,3; 同上一个; int array[3]; 仍然在堆栈上保留 3 个连续整数,但不初始化它们(内容将是随机垃圾) int array[4] = 1,2,3; 当初始化器列表未初始化所有元素时,其余元素设置为 0(C99 §6.7.8/19):在这种情况下,您将获得 1,2,3,0

请注意,在所有这些情况下,您并不是在分配新内存,您只是在使用已经提交给堆栈的内存。只有当堆栈已满时,您才会遇到问题(猜猜看,这将是 堆栈溢出)。因此,声明 int array[]; 是错误且毫无意义的。

要使用malloc,您必须声明一个指针:int* array

当你写int* array = malloc(3*sizeof(int));你实际上是在做三个操作:

    int* array 告诉编译器在堆栈上保留一个指针(一个包含内存地址的整数变量) malloc(3*sizeof(int)) 在堆上分配 3 个连续整数并返回第一个的地址 = 将返回值(您分配的第一个整数的地址)的副本分配给您的指针变量

所以,回到你的问题:

pthread_t tid[MAX_OPS];

是堆栈上的一个数组,因此不需要分配它(如果MAX_OPS 是,比如说,16,那么堆栈上将保留适合 16 pthread_t 所需的连续字节数)。此内存的内容将是垃圾(堆栈变量未初始化为零),但pthread_create 在其第一个参数中返回一个值(指向pthread_t 变量的指针)并忽略任何先前的内容,因此代码只是很好。

【讨论】:

对于int array[4],它们都已初始化。当初始化列表未初始化所有元素时,其余元素设置为 0/NULL (C99 §6.7.8/19)。 这令人困惑; “堆”和“动态分配”指的是同一件事。 “静态初始化”是指初始化静态变量,在谈到所谓的“堆栈”变量时并非如此。函数内部int array[3]; 中的分配类型是“自动分配”(或非正式的“堆栈”,有些系统没有堆栈),而不是“静态”。【参考方案3】:

C 提供静态内存分配以及动态 - 您可以在堆栈外或可执行内存(由编译器管理)中分配数组。这与在 Java 中的方式相同,您可以在堆栈上分配一个 int 或在堆上分配一个 Integer。 C 中的数组就像任何其他堆栈变量一样——它们超出范围等。在 C99 中它们也可以具有可变大小,尽管它们不能调整大小。

和 malloc/calloc 之间的主要区别在于 数组是静态分配的(不需要释放)并为您自动初始化,而 malloc/calloc 数组必须显式释放并且您必须显式初始化它们.但当然,malloc/calloc 数组不会超出范围,您可以(有时) realloc() 它们。

【讨论】:

只有在任何函数之外或明确标记为static的数组才是静态的;否则它们是自动的【参考方案4】:

2 - 这个数组声明是静态的:

pthread_t tid[MAX_OPS];

我们不需要分配内存块,而是动态分配:

pthread_t *tid = (pthread_t *)malloc( MAX_OPS * sizeof(pthread_t) );

别忘了释放内存:

free(tid);

3 - malloc 和 calloc 的区别在于 calloc 为数组分配一块内存,并将其所有位初始化为 0。

【讨论】:

那么第一个和第二个有什么区别呢?为什么要在第二行投射指针?对不起,如果我听起来很愚蠢,但这对我来说是全新的...... 好的,我刚看到你为什么要选角。尽管如此,第一行和第二行之间是否有任何实际区别,您可以将指针“移动”到您想要的任何内容? 静态声明比动态声明更安全,但您不能重新分配内存块以更改其大小。 您的 malloc 调用错误。 Malloc 需要许多 字节 而不是条目。 您忘记在malloc() 中将MAX_OPS 乘以sizeof *tid【参考方案5】:

我发现在使用 C(而不是 C++)编程时明确指示 *array 很有帮助,记住有一个可以移动的指针。因此,我想首先将您的示例改写为:

int array[] = 0,1,2;
int *array = malloc(3*sizeof(int));
int *array = calloc(3,sizeof(int));

第一个清楚地表明有一个叫做数组的东西指向一个包含 0、1 和 2 的内存块。数组不能移动到其他地方。

您的下一个代码: pthread_t tid[MAX_OPS];

实际上会导致分配 sizeof(pthread_t) * MAX_OPS 的数组。但是它没有分配一个叫做 *tid 的指针。数组的基地址是有的,但是不能移到别处。

ptherad_t 类型实际上是一个指针的覆盖。所以上面的tid实际上是一个指针数组。而且它们都是静态分配的,但没有初始化。

pthread_create 获取数组开头的位置 (&tid[0]),它是一个指针,并分配一块内存来保存 pthread 数据结构。设置指针指向新的数据结构,并分配数据结构。

您的最后一个问题 --- malloc(n*sizeof(int))calloc(n,sizeof(int)) 之间的区别在于后者将每个字节初始化为 0,而第一个则没有。

【讨论】:

那么,如果我声明:int array[] 它的内存已经分配了吗?那么它与声明指针然后使用malloc相同吗?再次感谢 @Hallucynogenyc:不,不一样。 int array[size] 从堆栈中分配。 int array[] = malloc() 在堆上。 在 C 中,这 3 行中的第一行只是 无效。它不会编译。

以上是关于我对 C 上的 malloc() 和 calloc() 感到非常困惑的主要内容,如果未能解决你的问题,请参考以下文章

C语言里,啥时候用数组啥时候用指针和动态内存(malloc/calloc)?

C ++中的“new”和“malloc”和“calloc”有啥区别? [复制]

C malloc和calloc函数总结

C:malloc/calloc/realloc/alloca内存分配函数

C -(malloc、calloc 或静态)从函数返回的 2d 字符数组

C中堆管理——浅谈malloc,calloc,realloc函数之间的区别