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

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

我觉得这很疯狂:你不能在没有释放它的情况下在堆上分配内存,而不能有一个非玩具程序 。 但是我没有足够的知识来解释为什么对每个malloc()必须有一个free()

所以:有没有在不使用free()情况下使用malloc()情况? 如果没有,我怎么能向我的教授解释呢?

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

确保内存不碎裂,通过将相邻的孔连接到较大的孔中,是非常普遍的。 更严重的分配器使用更严重的技术来确保这一点。

所以,假设你做了三次分配和取消分配,并按照以下顺序在内存中分配块:

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

单个分配的大小并不重要。 那么你释放了第一个和最后一个,A和C:

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

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

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

这可以被分解成正义

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

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

参考,请求:

  • 尝试阅读dlmalloc的代码。 作为一个完整的生产质量的实施,我更加先进。
  • 即使在embedded式应用程序中,也可以使用分片实现。 请参阅FreeRTOS中有关heap4.c代码的注释 。

其他的答案已经很好地解释了malloc()free()真正实现确实将漏洞合并(碎片整理)成更大的空闲块。 但即使不是这样,放弃free()也不是个好主意。

事情是,你的程序刚刚分配(并希望释放)这4个字节的内存。 如果要运行一段时间,很可能需要重新分配4个字节的内存。 所以即使这4个字节永远不会合并成一个更大的连续空间,它们仍然可以被程序本身重新使用。

这完全是无稽之谈,比如有很多不同的malloc实现,有些试图使得像Doug Lea或者这个那样更加高效。

你的教授有没有机会和POSIX合作? 如果他们习惯于编写大量的小型简约shell应用程序,那么我可以想象这种方法不会太糟糕 – 在操作系统的闲暇时间释放整个堆就比释放千variables。 如果您希望您的应用程序运行一两秒钟,则可以轻松脱离,而且完全不用解除分配。

当然,这还是一个不好的做法(性能改进应该总是build立在分析的基础上,而不是模糊的直觉),而不是在没有解释其他约束条件的情况下对学生说的话,但是我可以想象很多微小的pipe道shell – 用这种方式写的应用程序(如果不是直接使用静态分配的话)。 如果你正在从没有释放你的variables中受益的事情,你要么在极低的延迟条件下工作(在这种情况下,你甚至可以承担dynamic分配和C ++?:D),或者你做非常非常错误的事情(比如通过一个接一个地分配一个整数而不是一个内存块来分配一个整型数组)。

你提到他们是电子教授。 他们可能被用来编写固件/实时软件,能够准确地计时执行经常是需要的。 在这种情况下,如果你知道你有足够的内存来存储所有的分配空间,而不是释放和重新分配内存,那么对执行时间的计算可能会更容易一些。

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

从一个不同于以往的评论者和答案的angular度来看,一个可能性是你的教授已经有了静态分配内存系统的经验(即编译时)。

当你做这样的事情时,静态分配就来了:

 define MAX_SIZE 32 int array[MAX_SIZE]; 

在许多实时和embedded式系统(EEs或CEs最可能遇到的系统)中,通常最好避免dynamic内存分配。 所以,使用mallocnew和它们的删除对象是很less见的。 最重要的是,近年来电脑的内存已经爆炸了。

如果你有512 MB的可用空间,并且静态分配1 MB的空间,那么在软件爆炸之前,你大概需要511 MB的空间(好吧,不完全是…但是请跟我来)。 假设你有511 MB的滥用,如果你malloc每秒4字节没有释放他们,你将能够运行了近73小时,在内存不足之前。 考虑到许多机器每天closures一次,这意味着你的程序永远不会用完内存!

在上面的例子中,泄漏是每秒4个字节,或者240个字节/分钟。 现在想象你降低了这个字节/分钟的比例。 比率越低,程序运行的时间越长,没有问题。 如果你的malloc很less,这是一个真正的可能性。

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

拯救自己的痛苦和苦难,并消耗这个真理: malloc始终与一个free关联。 new应该总是有一个delete

我认为,如果从程序员的angular度来看,这个问题中提出的主张是无稽之谈,但是从操作系统的angular度来看,这是有道理的(至less有一些)。

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

在任何非平凡的程序中,即使你释放()大部分分配的内存,这个页面在进程生命周期中被回馈给操作系统的机会也非常小。 所以free()的内存大部分时间只能用于相同的进程,而不能用于其他进程。

你的教授没有错,但也是(他们至less是误导性或简单化)。 内存碎片导致性能和内存使用效率的问题,所以有时你必须考虑它并采取行动来避免它。 一个经典的诀窍是,如果你分配了大量相同大小的东西,那么在启动时获取一定数量的内存池,并在内部完全pipe理它的使用情况,从而确保你不会在操作系统级别(和内部存储器映射器中的洞将是该types的下一个对象的正确大小)。

有些整个第三方库只能为你处理这种事情,有时却是可以接受的性能和运行得太慢的东西之间的区别。 malloc()free()需要大量的时间来执行,如果你打电话给他们,你会注意到这一点。

所以通过避免只是使用malloc()free()可以避免碎片和性能问题 – 但是当你正确地认识到它的时候,你应该一直确保你free()你所有的malloc()除非你有一个好的理由不这样做。 即使在使用内部内存池的时候,一个好的应用程序也会在退出之前free()池内存。 是的,操作系统会清理它,但如果应用程序的生命周期后来改变,很容易忘记池还在附近…

长期运行的应用程序当然需要仔细清理或回收所有分配的内容,否则最终会耗尽内存。

你的教授提出重要的一点。 不幸的是,英文的用法是这样的,我不是很确定他们说了些什么。 让我回答一下这个问题,这个非玩具的程序有一定的内存使用特点,而且我亲自操作过。

一些程序的行为很好。 他们在波浪中分配内存:大量的小型或中型分配,然后是重复的循环。 在这些程序中,典型的内存分配器做得相当好。 他们合并释放的块,并在一波的结束大部分空闲的内存是在大块连续的块。 这些scheme相当罕见。

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

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

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

不知道你的教授说的是什么,真正的生产规模计划,我可能会站在他们的一边。

编辑

我会去回答一些批评。 显然,这样的职位并不是一个好的职位。 要明确一点:我有大约30年的编写这种软件的经验,包括几个编译器。 我没有学术资料,只是我自己的伤痕。 我不禁感受到来自更狭窄和更短的经验的人的批评。

我将重复我的关键信息:在实际程序中平衡malloc和free是解决大规模内存分配问题的一个不错的解决scheme。 块合并是正常的,并购买时间,但这是不够的。 你需要认真的,聪明的内存分配器,这往往会抓住内存块(使用malloc或其他),很less。 这可能是OP的教授们的想法,他误解了。

我很惊讶,没有人引用“书”

这最终可能并非如此,因为回忆可能会变得足够大,以至于在计算机的生命周期中不可能耗尽可用内存。 例如,一年大约有3⋅10 13微秒,所以如果我们每微秒消耗一次,我们将需要大约10 15个存储单元来构build一个可以运行30年而不会耗尽内存的机器。 那么多的回忆今天的标准似乎荒谬的大,但它并不是不可能的。 另一方面,处理器变得越来越快,未来的计算机可能会在单个内存上并行运行大量处理器,因此可能会比我们所假定的更快地使用内存。

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

所以,事实上,很多程序都可以做得很好,没有任何内存的麻烦。

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