C语言中的Malloc和Free函数

3

我正在尝试理解C语言中的函数mallocfree。我知道这个问题在StackOverflow上被广泛讨论过。但是,我现在有点了解这些函数的作用了。我想知道为什么要使用它们。让我们看一下这段代码:

int n = 10;
char* array;
array = (char*) malloc(n * sizeof(char));
// Check whether memory could be allocated or not...
// Do whatever with array...
free(array);
array = NULL;

我创建了一个char类型的指针,称之为array。然后我使用malloc来找到一块当前未被使用且大小为(10 * sizeof(char))字节的内存块。在将该地址分配给先前创建的char指针之前,我将其转换为char指针类型。现在我可以使用我的char数组了。完成后,我将使用free释放那块不再使用的内存块。
我有一个问题:为什么我不只是做char array[10];?维基百科只有一句简短的话来回答这个问题,而那句话我不幸地不理解:
“但是,数组的大小在编译时固定。如果希望动态分配类似的数组...”
我的大学讲义也同样简洁:
“还可以从堆中分配内存。”
堆是什么?我知道一个名为堆的数据结构。 :)
然而,如果有人能够解释一下在什么情况下使用mallocfree而不是常规变量声明是有意义的,那就太好了。 :)

1
可能是什么是堆栈和堆,它们在哪里?的重复问题。 - AShelly
1
不完全相同,但应该能帮助您理解差异。 - AShelly
2
我不认为这是一个重复的问题。了解堆是非常有帮助的,谢谢分享! - typeduke
6个回答

4

C提供了三种不同的对象“存储期限”:

  • 自动 - 本地存储器,仅针对所在函数的调用。如果从多个线程或递归调用函数,则可能创建使用自动存储器的对象的多个实例,也可能没有实例(当函数未被调用时)。

  • 静态 - 存在于整个程序运行期间的一个实例中的存储器。

  • 分配(动态)- 使用malloc创建,并持续到调用free释放它或程序终止为止。分配存储是唯一能够创建任意大或任意多对象并且即使函数返回这些对象也会被保留的存储类型。这就是malloc的实用性之处。


1
关于你的问题:“为什么我不只使用char array[10];?”。你可以这样做,而且大多数情况下,这将完全足够。然而,如果你想做类似的事情,但是要求更大,或者数据的大小需要在执行过程中改变怎么办?这些都是使用动态分配内存(calloc()或malloc())的情况。需要了解一些关于堆栈使用的知识:当你使用malloc()或calloc()时,它使用来自堆的内存,而自动/静态变量则在栈上获得内存,并在你离开该变量的作用域(即声明它的函数或块)时被释放。当所需数据的大小直到运行时才知道时,使用malloc和calloc非常有用。当大小确定后,你可以轻松调用它们之一来将内存分配到堆上,然后在完成后使用free()释放它。

关于堆是什么?这个话题有一个很好的讨论,可以在这里找到(略微不同的话题,但是有很好的讨论)

回答然而,我希望有人能解释一下在哪种情况下使用malloc()free()有意义...

简而言之,如果您在构建时(运行时之前)知道特定变量的内存需求,请使用静态/自动创建变量(及其相应的内存使用)。如果直到运行时才知道需要多大的内存,请使用malloc()calloc()以及相应的free()调用(每次使用)来创建内存。当然,这只是一个经验法则和粗略概括。随着使用内存的经验增加,您会发现即使在运行时之前已知大小信息,由于其他标准,您也会选择动态分配。(大小就是其中之一)


1
首先,没有必要强制转换 malloc
array = malloc(n * sizeof(char));   

我有一个问题:为什么不直接使用char array[10];
如果你不确定要使用多少存储空间(例如,如果你想要一个任意大小的数组,比如栈或链表),你会怎么做?在这种情况下,你必须依赖于malloc(在C99中,你可以使用 可变长度数组,但对于小内存大小的情况下)。 malloc函数用于在程序执行期间分配一定量的内存。该函数将从堆中请求一块内存。如果被批准,操作系统将保留所请求的内存量。
当不再需要内存时,必须通过调用函数free将其归还给操作系统。
简单来说:在编译时已知数组需要容纳的元素数量时,使用数组;在编译时不知道数组需要容纳多少元素时,使用指针和malloc。
有关详细信息,请阅读使用malloc()free()进行堆管理

所以你的意思是我不能像在Java中那样做char array[n],但我可以做array = malloc(n * sizeof(char));。如果我正确理解你的意思,那么如果我不知道需要多少内存,我必须使用malloc。也许因为这取决于某些用户输入。 - typeduke
@baerenfaenger:这个答案是错误的,因为它说你必须依赖于malloc;在C 1999年及以后的版本中,你可以使用char array[n];定义一个n在运行时确定的数组。然而,你应该只对小尺寸的数组使用此方法(因为为此分配的空间通常不是很大),并且以这种方式分配的数组不能从定义它的函数中返回,因为当函数退出时,它就不存在了。因此,在大多数动态分配的实际用途中,你将使用malloc或相关例程。 - Eric Postpischil
如果你在Java中使用char array[n];,它可能会导致Java引擎内部调用malloc()。当不再使用时,Java会自动释放malloc()的内容,并且Java提供的唯一保证是只要你使用它,数组就会被分配。然而,如果你在C中这样做,它将在堆栈上分配,并且保证在函数返回时进行清理。 - Atle
@EricPostpischil;我错过了C99中的VLA。 - haccks
@EricPostpischil:你所说的“因为为此功能提供的空间通常不是很大”是什么意思? - typeduke
1
@baerenfaenger:大多数C语言实现使用堆栈进行自动分配,在常见的系统(如OS X或Windows)上,堆栈通常为1到8兆字节左右。堆栈必须足够所有同时调用(嵌套)的例程使用。 - Eric Postpischil

1
假设你想分配1,000个数组。
如果你没有malloc和free...但需要在源代码中为每个数组声明,那么你就必须做1,000个声明。你必须给它们所有命名。 (array1,array2,... array1000)。
动态内存管理的总体思想是,在编写程序时,处理项目的数量是您无法预先知道的。

0
如果您事先知道只需要一个包含10个字符的数组,那么您应该直接声明char array[10]。如果您不知道需要多少存储空间,那么malloc就非常有用。如果您需要在当前函数返回后仍然有效的存储空间,那么malloc也非常有用。如果您将数组声明为char array[10],它将被分配在堆栈上。这些数据在函数返回后将无效。从malloc获得的存储空间在调用free之前都是有效的。
此外,没有必要对malloc的返回值进行强制类型转换。

0

为什么在使用malloc后要使用free可以这样理解,将内存释放掉是一个很好的编程风格,尽快释放不再需要的内存。但如果你不释放内存,对系统来说并没有太大的影响,只会增加运行时的成本。

当你退出程序时,也可以选择不释放内存。malloc() 使用堆,进程退出时会释放整个堆。人们坚持释放内存的唯一原因是为了避免内存泄漏。

这里获取更多信息:

分配迷思4:非垃圾回收程序应该总是释放它们分配的所有内存。
真相:在频繁执行的代码中省略了释放操作会导致内存泄漏不断增加。它们很少被接受,但保留大部分分配的内存直到程序退出的程序通常在没有任何干预的情况下表现更好。如果没有free,Malloc要容易得多。
在大多数情况下,在程序退出之前释放内存是毫无意义的。操作系统将无论如何回收它。Free会触及并分页死亡对象;操作系统不会这样做。
结果:小心“泄漏检测器”,它们计算分配量。有些“泄漏”是好的!

维基也提到了堆内存分配的一个好点:

堆内存分配方法存在一些固有缺陷,完全源于碎片化。与任何内存分配方法一样,堆将变得碎片化;也就是说,在堆上分配的空间中会有已使用和未使用的内存部分。一个好的分配器将尝试找到一个未使用的已分配内存区域来使用,而不是扩展堆。这种方法的主要问题在于堆只有两个重要属性:基址或虚拟内存空间中堆的起始位置;长度或大小。堆需要足够的系统内存来填充其整个长度,而其基址永远不能改变。因此,任何大面积未使用的内存都是浪费的。如果堆末端存在一个小的已使用段,那么堆可能会“卡住”,这可能会浪费任何数量的地址空间,从几兆字节到几百兆字节不等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接