在分配的内存上*不*使用free()是否可以?

时间:2014-03-18 13:38:37

标签: c++ c memory-management memory-leaks heap

我正在学习计算机工程,我有一些电子课程。我听说,我的两位教授(这些课程中)可以避免使用free()函数(在malloc()calloc()之后等),因为可能分配的内存空间将不再用于分配其他内存。也就是说,例如,如果您分配4个字节然后释放它们,您将有4个字节的空间可能不会再次分配:您将有一个

我认为这很疯狂:你不能拥有 not-toy-program ,你可以在堆上分配内存而不释放它。但我没有足够的知识来解释为什么它对于每个malloc()来说非常重要,必须有free()

那么:在某些情况下,在不使用malloc()的情况下使用free()可能是合适的吗?如果没有,我该如何向我的教授解释这一点?

11 个答案:

答案 0 :(得分:100)

简单:只需阅读几乎任何半严重malloc()/free()实施的来源。通过这个,我的意思是处理调用工作的实际memory manager。这可能位于运行时库,虚拟机或操作系统中。当然,在所有情况下都无法平等地访问代码。

通过将相邻的孔连接到较大的孔中来确保存储器没有碎片,这是非常常见的。更严重的分配器使用更严格的技术来确保这一点。

所以,让我们假设你做了三次分配和解除分配,并按顺序获取内存中的块:

+-+-+-+
|A|B|C|
+-+-+-+

个人分配的大小并不重要。然后你释放第一个和最后一个,A和C:

+-+-+-+
| |B| |
+-+-+-+

当你最终释放B时,你(最初,至少在理论上)最终得到:

+-+-+-+
| | | |
+-+-+-+

可以拆分为

+-+-+-+
|     |
+-+-+-+

即。一个较大的自由块,没有碎片留下。

根据要求提供参考:

答案 1 :(得分:42)

其他答案已经很好地解释了malloc()free()的实际实现确实将(defragmnent)漏洞合并为更大的空闲块。但即使情况并非如此,放弃free()仍然是一个坏主意。

问题是,你的程序刚刚分配(并且想要释放)那4个字节的内存。如果它要运行一段时间,很可能需要再次分配4个字节的内存。因此,即使这4个字节永远不会合并到更大的连续空间中,它们仍然可以被程序本身重用。

答案 2 :(得分:10)

这完全是胡说八道,例如malloc有许多不同的实现,有些尝试使堆更高效,如Doug Lea'sthis

答案 3 :(得分:9)

您的教授是否有机会与POSIX合作?如果他们习惯于编写许多小型的,简约的shell应用程序,那么我可以想象这种方法不会太糟糕 - 在闲暇之后立即释放整个堆栈。 OS 比释放一千个变量更快。如果您希望您的应用程序运行一两秒钟,您可以很容易地完全没有取消分配。

当然,这仍然是一种不好的做法(性能改进应始终基于剖析,而不是模糊的直觉),如果不解释其他限制,你不应该对学生说些什么,我可以想象很多小管道shell应用程序都是这样编写的(如果不是直接使用静态分配)。如果您正在从事不会释放变量的好处,那么您要么在极低延迟条件下工作(在这种情况下,您怎么能负担动态分配和C ++?:D),或者你做了非常非常错误的事情(比如通过一个接一个地分配一千个整数而不是一个单独的内存块来分配整数数组)。

答案 4 :(得分:5)

你提到他们是电子学教授。它们可能用于编写固件/实时软件,能够准确地计算时间,通常需要执行。在那些知道你有足够的内存用于所有分配而不释放和重新分配内存的情况下,可以给出更容易计算的执行时间限制。

在某些方案中,硬件内存保护也可用于确保例程在其分配的内存中完成,或者在非常特殊情况下生成陷阱。

答案 5 :(得分:2)

从与以前的评论者和答案不同的角度来看,一种可能性是你的教授有过静态分配记忆的系统的经验(即:编译程序的时候)。

当您执行以下操作时会发生静态分配:

define MAX_SIZE 32
int array[MAX_SIZE];

在许多实时和嵌入式系统(最有可能遇到EE或CE的系统)中,通常最好完全避免动态内存分配。因此,mallocnew及其删除对应物的使用很少见。最重要的是,近年来计算机中的内存已经爆炸式增长。

如果你有512 MB的可用空间,并且静态分配1 MB,那么在你的软件爆炸之前,你有大约511 MB可以完成任务(好吧,不完全......但请跟我一起去)。假设你有滥用511 MB,如果你没有释放它们每秒malloc 4个字节,你将能够在内存不足之前运行近73个小时。考虑到每天关闭许多机器,这意味着您的程序永远不会耗尽内存!

在上面的例子中,泄漏是每秒4个字节,或240个字节/分钟。现在假设您降低了字节/分钟比率。该比率越低,程序运行的时间越长,没有问题。如果您的malloc不常见,那么这是一种真正的可能性。

哎呀,如果你知道你只会去malloc一次,那malloc永远不会再被击中,那么它就像静态分配一样,尽管你不需要知道你在前面分配的内容的大小。例如:假设我们再次拥有512 MB。我们需要malloc 32个整数数组。这些是典型的整数 - 每个4个字节。我们知道这些数组的大小永远不会超过1024个整数。我们的程序中没有其他内存分配。我们有足够的记忆吗? 32 * 1024 * 4 = 131,072。 128 KB - 是的。我们有足够的空间。如果我们知道永远不会分配更多的内存,我们可以安全地malloc这些数组而不释放它们。但是,这也可能意味着如果程序崩溃,则必须重新启动机器/设备。如果你开始/停止你的程序4,096次,你将分配所有512 MB。如果你有僵尸进程,那么即使在崩溃之后也永远不会释放内存。

拯救自己痛苦和痛苦,并将此口头禅作为一个真理:malloc始终free相关联。 new应该始终拥有delete

答案 6 :(得分:2)

我认为如果从程序员的角度来看,问题中所述的声明是无稽之谈,但它从操作系统的角度来看是真实的(至少有一些)。

malloc()最终会调用mmap()或sbrk()来获取操作系统中的页面。

在任何非平凡的程序中,即使你释放大部分已分配的内存,在进程生命周期内将此页面返回给操作系统的可能性也非常小。因此,free()内存只能在大多数时间用于同一个进程,而不能用于其他进程。

答案 7 :(得分:2)

你的教授没有错,但也是(他们至少误导或过度简化)。内存碎片会导致性能问题和内存的有效使用,因此有时您必须考虑它并采取措施来避免它。一个经典的技巧是,如果你分配了大量相同大小的东西,在启动时抓住一个内存池,这是该大小的一部分并完全在内部管理它的使用,从而确保你没有发生碎片。操作系统级别(内部内存映射器中的孔将与该类型的下一个对象的大小完全相同)。

整个第三方库除了为您处理这类事情之外什么都不做,有时它是可接受的性能和运行得太慢的事情之间的区别。 malloc()free()会花费相当多的时间来执行,如果你经常打电话,你会开始注意到这一点。

因此,通过避免天真地使用malloc()free(),您可以避免碎片和性能问题 - 但是当您接下来时,您应该始终确保free()一切你malloc(),除非你有充分的理由不这样做。即使使用内部内存池,一个好的应用程序也会在池内存退出之前free()。是的,操作系统会清理它,但是如果应用程序生命周期后来改变了,那么很容易忘记那个游泳池还在闲逛......

长期运行的应用程序当然需要非常谨慎地清理或回收它们分配的所有内容,否则它们最终会耗尽内存。

答案 8 :(得分:1)

你的教授提出了一个重点。不幸的是,英语用法是这样的,我不能完全确定他们说的是什么。让我以非玩具程序的方式回答这个问题,这些程序具有一定的内存使用特性,并且是我亲自合作过的。

有些程序表现很好。他们在波浪中分配内存:在重复周期中进行大量的小型或中型分配,然后进行大量释放。在这些程序中,典型的内存分配器做得相当好。它们合并了释放的块,并且在波浪结束时,大部分可用内存都是大块连续的块。这些计划非常罕见。

大多数程序表现不佳。它们或多或少地随机分配和释放内存,从非常小到非常大的各种大小,并且它们保留了分配块的高使用率。在这些程序中,合并块的能力是有限的,并且随着时间的推移它们完成了高度分散且相对不连续的存储器。如果32位内存空间中的总内存使用量超过大约1.5GB,并且有(例如)10MB或更多的分配,则最终其中一个大分配将失败。这些计划很常见。

其他程序可以释放很少或没有记忆,直到它们停止。它们在运行时逐步分配内存,仅释放少量内存,然后停止,此时释放所有内存。编译器是这样的。 VM也是如此。例如,.NET CLR运行时本身用C ++编写,可能永远不会释放任何内存。为什么要这样?

这是最后的答案。在程序内存使用量足够大的情况下,使用malloc和free管理内存并不足以解决问题。除非你足够幸运地处理一个行为良好的程序,否则你需要设计一个或多个自定义内存分配器,预先分配大块内存,然后根据你选择的策略进行子分配。除非程序停止,否则您可能根本不使用免费。

如果不确切知道你的教授所说的话,对于真正的生产规模课程,我可能会站出来。

修改

我会回答一些批评。显然,对于这类职位来说,SO不是一个好地方。需要明确的是:我有大约30年的编写这种软件的经验,包括几个编译器。我没有学术参考,只有我自己的伤痕。我不禁感到批评来自于经验范围更窄,更短的人。

我将重复我的关键信息:平衡malloc和free 是实际程序中大规模内存分配的充分解决方案。块合并是正常的,并且购买时间,但它不足。你需要认真,聪明的内存分配器,它们倾向于以块(使用malloc或其他)获取内存并且很少免费。这可能是OP教授的想法,他误解了这一点。

答案 9 :(得分:1)

我很惊讶没有人引用The Book

  

这最终可能不是真的,因为记忆可能变得足够大,以至于在计算机的生命周期中不可能耗尽空闲内存。例如,一年中大约有3×10 13 微秒,所以如果我们每微秒一次,我们需要大约10个 15 的内存单元来构建一个机器可以运行30年而不会耗尽内存。按照今天的标准,这么多的记忆似乎太荒谬了,但实际上并非不可能。另一方面,处理器变得越来越快,未来的计算机可能会在单个内存上并行运行大量处理器,因此可能比我们假设的更快地耗尽内存。

http://sarabander.github.io/sicp/html/5_002e3.xhtml#FOOT298

所以,实际上,许多程序可以做得很好,而不必费心去释放任何内存。

答案 10 :(得分:1)

我知道一个明确释放内存比无用更糟糕的情况。也就是说,当您需要所有数据,直到流程生命周期结束。换句话说,只有在程序终止之前才能释放它们。由于任何现代操作系统在程序死亡时都会占用内存,因此在这种情况下不需要调用free()。实际上,它可能会减慢程序终止速度,因为它可能需要访问内存中的多个页面。