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


83

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

我认为这很疯狂:您不能拥有一个非玩具程序,在其中不释放内存就在堆上分配内存。但是我没有知识来确切解释为什么它如此重要以至于每个人都malloc()必须有一个free()

因此:在任何情况下都可以malloc()不使用而使用afree()吗?如果没有,我该如何向教授解释?


11
它们不是“错误的”-它们对非常小的孤立的自由区域的碎片化有一个有效的观点(如果有限制的话),并且可能比您所报告的要谨慎得多。
克里斯·斯特拉顿

2
唯一不需要释放内存的时间是在使用托管内存或要重新使用原始分配的内存时。我怀疑两位讲师之所以说这是因为您正在谈论一个可以重用内存的特定程序。在这种情况下,您仍将使用free(),但仅在应用程序末尾使用。您确定这不是他们的意思吗?
krowe 2014年

17
@Marian:我曾有一位教授声称,在C和C ++中,有必要在与分配的同一.c / .cxx文件中定义的函数中释放已分配的内存...这些人有时似乎严重缺氧由于住在象牙塔里太高了。
PlasmaHH 2014年

4
有很多非玩具程序不会释放内存,并且让OS在进程退出时清理所有内存比(轻松地)保持大量簿记快得多,因此您可以自己做。
Donal Fellows

9
绝对不要将听到的东西毫无疑问地融合到大脑中。我有很多错误的或过时的老师,讲师和纠正者。并始终非常准确地分析他们所说的话。我们的人通常很精确,可能会说正确的话,但是很容易被误以为是被普通的家喻户晓的人误解或被错误地优先考虑。例如,我记得在学校里,有位老师说:“你做了功课吗?”,我说“不。” 虽然我是正确的,但老师发现了这种反感,因为我节省了我寻找to脚借口的时间,这是他没想到的。
塞巴斯蒂安·马赫2014年

Answers:


100

容易:只需阅读几乎任何半认真的malloc()/free()实现的源代码。借此,我指的是处理调用工作的实际内存管理器。这可能在运行时库,虚拟机或操作系统中。当然,并非在所有情况下都平等地访问该代码。

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

因此,假设您进行了三个分配和解除分配,并按以下顺序将块放置在内存中:

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

各个分配的大小无关紧要。然后您释放第一个和最后一个A和C:

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

当您最终释放B时,您(至少从理论上来说)最终会得到:

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

可以将其碎片化为

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

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

参考,根据要求:


1
能给我一些参考吗?
尼克

4
我认为,值得一提的是虚拟地址空间不是物理内存的直接表示。物理内存中的碎片可以由OS处理,而不是由进程释放的虚拟内存也不会被物理释放。
lapk 2014年

@PetrBudnik,无论如何,虚拟内存无论如何都不会将1-1映射到物理内存,操作系统会考虑页面映射并能够以最小的麻烦进行进出交换
棘手的问题2014年

3
非常挑剔的评论:虽然对概念进行了很好的解释,但实际示例还是有点..倒霉。对于任何看过dlmalloc源代码并感到困惑的人:特定大小以下的块始终是2的幂,并且会相应地合并/分割。因此(可能)我们最终得到一个8字节块和1个4字节块,但没有12字节块。至少在台式机上,这是分配器的一种非常标准的方法,尽管嵌入式应用程序可能会在开销方面更加谨慎。
Voo

@Voo我在示例中删除了块大小的提法,无论如何都没关系。更好?
放松

42

其他答案已经很好地解释了,实际上的实现malloc()free()确实将(碎片)漏洞合并为较大的免费块。但是即使不是那样,放弃仍然是一个坏主意free()

关键是,您的程序刚刚分配了(并希望释放)这4个字节的内存。如果要长时间运行,很可能只需要再次分配4个字节的内存即可。因此,即使这4个字节永远不会合并到更大的连续空间中,它们仍然可以被程序本身重新使用。


6
恰好+1。问题是,如果free被调用足够多的时间对性能有影响,那么它可能也被调用了足够多的时间,以至于将其遗漏会在可用内存中造成很大的损失。很难想象嵌入式系统上的性能会因以下原因而持续受到影响的情况,free但这种malloc情况仅被称为有限次数;拥有嵌入式设备先执行一次数据处理然后重置的情况很少见。
杰森·C


9

您的教授有机会与POSIX合作吗?如果他们已经习惯了写大量的小,简约的外壳应用程序,这是一个场景,我能想象这种做法也不会太糟糕-在一次在OS的休闲释放整个堆不是释放更快一千个变量。如果您希望您的应用程序运行一两秒钟,那么您就可以轻松地完成迁移,而无需进行任何重新分配。

当然,这仍然是一种错误的做法(性能改进应该始终基于分析而不是模糊的直觉),并且在没有解释其他限制的情况下就应该对学生说这句话,但是我可以想象到很多小管道-以这种方式编写的应用程序(如果未完全使用静态分配)。如果您正在从事的工作是受益于不释放变量,那么您要么在极低延迟的条件下工作(在这种情况下,您甚至不能负担得起动态分配和C ++?:D),或者您正在做的事情非常非常错误(例如通过依次分配一千个整数而不是单个内存块来分配整数数组)。


让OS最终释放所有内容不仅关系到性能-还需要更少的逻辑来正常工作。
hugomg 2014年

@missingno换句话说,它使您摆脱了内存管理的困难程度:)但是,我认为这是针对非托管语言的一个论据-如果您的原因是复杂的释放逻辑而不是性能,那么您可能会更好停止使用为您服务的语言/环境。
a安2014年

5

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

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


10
那是个很好的观点。但是,我希望malloc在那种情况下它们根本不会使用诸如此类,而是依靠静态分配(或者分配一个大块,然后手动处理内存)。
a安

2

从与以前的评论和答案不同的角度来看,这是一种可能性,就是您的教授在静态分配内存的系统(即程序编译时)上有经验。

静态分配是在您执行以下操作时出现的:

define MAX_SIZE 32
int array[MAX_SIZE];

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

如果您有512 MB的可用空间,并且静态分配了1 MB,那么在软件爆炸之前,您大约有511 MB可以通过(嗯,不完全是。。。请在这里与我联系)。假设您有511 MB的滥用空间,如果每秒不分配空间就分配4个字节,那么您将能够运行将近73个小时,然后再用尽内存。考虑到很多机器每天关闭一次,这意味着您的程序将永远不会耗尽内存!

在上面的示例中,泄漏为每秒4个字节或240个字节/分钟。现在,假设您降低了字节/分钟比率。该比率越低,程序可以运行得越久而不会出现问题。如果您的mallocs很少见,那确实是有可能的。

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

拯救自己的痛苦和痛苦,并将这一口头禅作为“唯一真理”:malloc应该始终与关联freenew应该总是有一个delete


2
在大多数嵌入式和实时系统中,仅在73小时后引起故障的定时炸弹将是一个严重的问题。
Ben Voigt 2014年

典型的整数??? 整数至少为16位,在小型微芯片上通常为16位。通常有更多的设备具有sizeof(int)等于2,而4
ST3

2

我认为,从字面上看,从程序员的角度来看,问题中提出的主张是胡说八道,但是从操作系统的角度来看,它具有真理(至少是某些事实)。

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

在任何不平凡的程序中,即使您释放()了大部分已分配的内存,在进程生命周期中将该页面还给操作系统的机会也很小。因此,free()的内存在大多数情况下仅对同一进程可用,而对其他进程则不可用。


2

您的教授没有错,但也有错(他们至少在误导或过于简化)。内存碎片会导致性能和内存使用效率方面的问题,因此有时您必须考虑并采取措施避免内存碎片。一个经典的窍门是,如果您分配了很多大小相同的东西,那么在启动时会抢占该大小的数倍的内存池,并完全在内部管理其使用情况,从而确保您不会在内存碎片发生时操作系统级别(并且内部内存映射器中的孔将恰好是随之而来的下一个对象的正确大小)。

整个第三方库除了为您处理这类事情外什么都不做,有时是性能可接受与运行速度太慢之间的区别。malloc()free()花费大量时间执行,如果您经常打电话给他们,您会开始注意到。

因此,通过避免天真地使用malloc()free()可以避免碎片化和性能问题-但是当您处理问题时,除非有充分的理由,否则应始终确保free()一切都准备就绪malloc()。即使使用内部内存池,一个好的应用程序也会free()在内存退出之前将其保留。是的,操作系统会清理它,但是如果以后更改应用程序生命周期,则很容易忘记池仍在闲逛...

当然,长期运行的应用程序在清除或回收已分配的所有内容时必须格外谨慎,否则最终会耗尽内存。


1

您的教授提出了一个重要的观点。不幸的是,英语用法是如此,我不能完全确定他们说的是什么。让我用具有某些内存使用特性并且与我个人合作过的非玩具程序来回答这个问题。

一些程序表现良好。它们以波浪的形式分配内存:在重复周期中,大量的中小型分配,然后是大量的释放。在这些程序中,典型的内存分配器运行良好。它们合并释放的块,并且在一波结束时,大多数空闲内存都位于大的连续块中。这些程序很少见。

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

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

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

不知道您的教授到底怎么说,对于真正的生产规模计划,我可能会站在他们一边。

编辑

我将一口气回答一些批评。显然,SO不是此类职位的好地方。需要明确说明的是:我有大约30年的编写此类软件的经验,其中包括一些编译器。我没有学术参考,只有我自己的瘀伤。我忍不住觉得批评来自经验狭窄和经验短的人。

我会重复我的关键信息:平衡malloc和free是足够的解决方案的大型存储器分配在实际程序。块合并是正常的,并且会花费时间,但是这不够。您需要认真,聪明的内存分配器,它们倾向于以块(使用malloc或其他方式)抢占内存,并且很少释放内存。这可能是OP的教授们想起的信息,他对此误解了。


1
不良的行为程序表明,体面的分配器应为不同大小的块使用单独的池。对于虚拟内存,相隔许多不同的池应该不是主要问题。如果每个块都被四舍五入为2的幂,并且每个池因此都包含只有一个大小的四舍五入的块,我将看不到碎片会变得多么糟糕。在最坏的情况下,程序可能突然对某个特定大小的范围完全失去兴趣,从而留下了一些很大的空池以供使用。我认为这不是很常见的行为。
Marc van Leeuwen 2014年

5
声称熟练编写程序在应用程序关闭之前不会释放内存的引用非常需要引用。

12
整个答案读起来像一系列随机的猜测和假设。有什么要备份的吗?
克里斯·海斯

4
我不确定说.NET CLR运行时不会释放任何内存是什么意思。据我测试,如果可以的话。
Theodoros Chatzigiannakis 2014年

1
@vonbrand:GCC有多个分配器,包括自己的垃圾收集器品牌。它在遍历期间消耗内存,并在遍历之间收集内存。大多数其他语言的编译器最多具有2个遍,并且在遍之间释放很少或没有内存。如果您不同意,我很乐意研究您提供的任何示例。
david.pfx 2014年

1

我很惊讶没有人引用《书》

最终可能并非如此,因为内存可能会变得足够大,以致在计算机的整个生命周期内都不可能耗尽可用内存。例如,一年大约有3⋅10 13微秒,因此,如果我们每微秒一次耗电,我们将需要大约10 15个内存单元来构建可以运行30年而不会耗尽内存的计算机。以今天的标准来看,这么大的内存似乎荒唐可笑,但实际上并不是不可能的。另一方面,处理器正在变得越来越快,并且未来的计算机可能在单个内存上具有大量并行运行的处理器,因此使用内存可能比我们假设的要快得多。

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

因此,实际上,许多程序可以很好地工作而无需费心释放任何内存。


1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.