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

Is it ever OK to *not* use free() on allocated memory?

本文关键字:使用 内存 分配 free      更新时间:2023-10-16

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

我认为这是疯狂的:你不能有一个not-toy-program在堆上分配内存而不释放它。但是我没有足够的知识来解释为什么每个malloc()都必须有一个free()是如此重要。

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

简单:只需阅读几乎任何半认真的malloc()/free()实现的源代码。这里,我指的是处理调用工作的实际内存管理器。这可能在运行时库、虚拟机或操作系统中。当然,代码不是在所有情况下都同样可访问。

通过将相邻的孔连接成更大的孔来确保内存不被碎片化,这是非常非常常见的。更严格的分配器使用更严格的技术来确保这一点。

那么,让我们假设你执行了三次分配和取消分配,并按照以下顺序获得内存中的块:

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

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

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

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

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

可以被分解成

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

。一个更大的空闲块,没有碎片。

引用:

    试着读取dlmalloc的代码。它更高级,是一个完全生产质量的实现。
  • 即使在嵌入式应用程序中,碎片整理实现也是可用的。参见FreeRTOS中heap4.c代码的注释。

其他答案已经很好地解释了malloc()free()的实际实现确实将漏洞合并(碎片整理)成更大的自由块。但即使不是这样,放弃free()仍然是一个坏主意。

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

这完全是胡说八道,例如,malloc有许多不同的实现,有些试图使堆更有效,如Doug Lea的或这个。

你们的教授在用POSIX吗?如果他们习惯于编写大量小型的、极简的shell应用程序,那么我可以想象这种方法不会太糟糕——在操作系统空闲的时候立即释放整个堆比释放一千个变量要快。如果您希望您的应用程序运行一两秒钟,那么您可以很容易地不取消分配。

当然,这仍然是一个不好的做法(性能改进应该始终基于分析,而不是模糊的直觉),而且你不应该在没有解释其他约束的情况下告诉学生,但我可以想象很多小管道shell应用程序都是以这种方式编写的(如果不直接使用静态分配)。如果你正在做一些从不释放变量中获益的事情,你要么在极低延迟的条件下工作(在这种情况下,你怎么能负担得起动态分配和c++ ?:D),或者您正在做一些非常非常错误的事情(例如通过一个接一个地分配一千个整数而不是单个内存块来分配整数数组)。

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

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

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

静态分配发生在以下情况:

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永远不会再被击中,那么它很像静态分配,尽管你不需要知道你分配的东西的大小。比方说我们又有了512mb的空间。我们需要malloc 32个整数数组。这些都是典型的整数——每个4字节。我们知道这些数组的大小永远不会超过1024个整数。在我们的程序中没有发生其他内存分配。我们有足够的内存吗?32 * 1024 * 4 = 131,072。128 KB -是的。我们有足够的空间。如果我们知道我们永远不会分配更多的内存,我们可以安全地malloc这些数组而不释放它们。但是,这也可能意味着如果程序崩溃,您必须重新启动机器/设备。如果你启动/停止你的程序4,096次,你将分配所有的512 MB。如果你有僵尸进程,有可能内存永远不会被释放,即使在崩溃之后。

为自己节省痛苦和痛苦,并将此咒语作为一个真理:malloc应该始终free相关联。new应该总是有一个delete

如果从程序员的角度来看,我认为问题中所述的主张是无稽之谈,但从操作系统的角度来看,它是正确的(至少部分正确)。

malloc()最终将调用mmap()或sbrk(),它们将从操作系统获取页面。

在任何重要的程序中,这个页面在进程生命周期内返回给操作系统的机会非常小,即使您释放了大部分已分配的内存。所以free()的内存大部分时间只对同一个进程可用,而对其他进程无效。

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

有完整的第三方库,它们什么也不做,只是为你处理这类事情,有时它是可接受的性能和运行速度太慢的区别。malloc()free()的执行需要相当多的时间,如果你经常调用它们,你就会开始注意到这一点。

因此,通过避免天真地使用malloc()free(),您可以避免碎片和性能问题-但是当您开始使用它时,您应该始终确保free()malloc()相同,除非您有很好的理由不这样做。即使在使用内部内存池时,一个好的应用程序也会在退出之前将池内存free()。是的,操作系统会清理它,但如果应用程序的生命周期后来改变了,它很容易忘记池仍然挂在…

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

你的教授提出了一个重要的观点。不幸的是,英语的用法是这样的,我不能完全确定他们说的是什么。让我从具有特定内存使用特征的非玩具程序的角度来回答这个问题,并且我亲自使用过。

有些程序表现得很好。它们以波的形式分配内存:在重复的循环中,大量的小型或中型分配,然后是大量的释放。在这些程序中,典型的内存分配器做得相当好。它们将释放的块合并在一起,在一个波结束时,大部分空闲内存都在连续的大块中。这些程序是非常罕见的。

大多数程序的行为都很糟糕。它们或多或少是随机分配和释放内存的,大小从非常小到非常大,并且它们保留了分配块的高使用率。在这些程序中,合并块的能力是有限的,随着时间的推移,它们最终会导致内存高度碎片化和相对不连续。如果在32位内存空间中,总内存使用量超过1.5GB,并且有10MB或更多的分配(例如),那么最终其中一个较大的分配将失败。这些程序很常见。

其他程序释放很少或不释放内存,直到它们停止。它们在运行时逐步分配内存,只释放少量内存,然后停止,此时释放所有内存。编译器就是这样的。虚拟机也是如此。例如,. net CLR运行时本身是用c++编写的,可能永远不会释放任何内存。为什么呢?

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

在不知道教授们到底说了什么的情况下,对于真正的生产规模项目,我可能会站在他们这一边。

<标题>编辑我将试着回答一些批评。很明显SO不是发布这类帖子的好地方。需要说明的是:我有大约30年编写这类软件的经验,包括编写过几个编译器。我没有学术证明,只有我自己的伤痕。我不禁感到这些批评来自那些经验更狭隘、更短浅的人。 我将重复我的关键信息:平衡malloc和free是不是在实际程序中大规模内存分配的充分解决方案。块合并是正常的,可以争取时间,但是不够。您需要认真的、聪明的内存分配器,它倾向于以块(使用malloc或其他)获取内存,并且很少释放。这可能是OP的教授们想要传达的信息,而他误解了。

我很惊讶居然没有人引用《圣经》:

这最终可能不是真的,因为内存可能变得足够大,以至于在计算机的生命周期内不可能耗尽可用内存。例如,一年大约有3⋅1013微秒,所以如果我们每微秒犯一次错误,我们将需要大约1015单元的内存来构建一台可以运行30年而不会耗尽内存的机器。按照今天的标准,这么大的内存似乎大得离谱,但从物理上讲,这并非不可能。另一方面,处理器正变得越来越快,未来的计算机可能在单个内存上并行运行大量处理器,因此可能比我们设想的更快地耗尽内存。

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

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

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