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

83
我正在学习计算机工程,学了一些电子课程。我从两位教授(这些课程的教授)那里听说过,可以避免使用free()函数(在使用malloc()calloc()等之后),因为分配的内存空间可能不会再被用来分配其他内存。 也就是说,例如,如果您分配了4个字节,然后释放了它们,您将有一个不太可能再次分配的4个字节的空间:您将拥有一个“空洞”。
我认为这很疯狂:你不能有一个真正的程序,在堆上分配内存而不释放它。但是我没有足够的知识来解释为什么对于每个malloc()都必须有一个free()是如此重要。
那么,是否有情况可以在不使用free()的情况下使用malloc()?如果没有,我该如何向我的教授解释?
答:一般情况下,每个malloc()都必须有一个相应的free()来释放已分配的内存,否则会导致内存泄漏。在某些情况下,比如在进程结束时,操作系统会自动回收堆上的内存,但这并不是一个可靠的做法。因此,为了确保程序的正确性和健壮性,在堆上分配内存后必须使用free()释放它们。

12
他们并不是“错误的”——他们对于非常小的孤立自由地区的分割有一个有效的(尽管有限)观点,并且可能比你所报告的要更加仔细地陈述了这一观点。 - Chris Stratton
18
@Marian :我曾经有一位教授声称,在 C 和 C++ 中,需要在与分配内存相同的 .c/.cxx 文件中定义的函数中释放已分配的内存……这些人有时似乎严重患有缺氧症,因为他们生活在象牙塔中太高了。 - PlasmaHH
4
有相当数量的非玩具程序不会释放内存,在进程退出时让操作系统清理所有内存比(繁琐地)保留大量簿记以便自行处理要快得多。 - Donal Fellows
2
正如@mfro所指出的那样,free()不会将内存返回给操作系统,但仍会标记为malloc()重用。这将推迟对mmap()/sbrk()的未来调用,并节省系统内存。一旦整个页面未使用,它们将被分页。然而,长期运行的进程应该解决这个问题并实际将内存返回给操作系统(使用mmap()brk()更容易,但重新启动进程要容易得多)。这就是开发和运营需要理解其影响的地方。 - Henk Langeveld
9
不要毫无怀疑地接受你听到的内容。我曾经遇到很多老师、演讲者和校正者是错误或过时的。一定要非常精确地分析他们所说的话。我们的民族通常非常精确,可能会说正确的话,但某些人如果只擅长于日常用语,很容易理解错误或以错误的优先级理解。例如,我记得在学校有一位老师问:“你做作业了吗?”我回答:“没有。”虽然我是正确的,但是老师觉得这样回答很冒犯,因为我省去了找借口的时间,这是他意料之外的。 - Sebastian Mach
显示剩余8条评论
11个回答

1

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

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

大多数程序表现得很差。它们随机地分配和释放内存,大小各异,从非常小到非常大,并保留高比例的分配块。在这些程序中,合并块的能力是有限的,随着时间的推移,它们最终会出现高度碎片化和相对不连续的内存。如果32位内存空间中的总内存使用量超过1.5GB,并且有10MB或更大的分配,则最终会失败其中一个大的分配。这些程序很常见。

其他程序在运行时很少或不占用内存,直到它们停止。它们在运行时逐渐分配内存,仅释放少量内存,然后停止,此时所有内存都被释放。编译器就是这样的。虚拟机也是如此。例如,.NET CLR运行时本身是用C++编写的,可能从不释放任何内存。为什么要这样做呢?
这就是最终答案。在那些内存使用量足够大的情况下,使用malloc和free来管理内存并不能解决问题。除非你足够幸运地处理一个表现良好的程序,否则你需要设计一个或多个自定义内存分配器,预先分配大块内存,然后根据你选择的策略进行子分配。你可能根本不使用free,除非程序停止。
如果不知道你的教授们具体说了什么,在真正的生产规模程序中,我可能会站在他们的立场上。
编辑
我会试着回答一些批评。显然,Stack Overflow不是这种帖子的好地方。只是为了明确:我有约30年的编写此类软件的经验,包括几个编译器。我没有学术参考资料,只有自己的经验。我无法帮助感觉批评来自经验远远不及我的人。
我会重复我的关键信息:在实际程序中,平衡 malloc 和 free 并不足以解决大规模内存分配的问题。块合并是常见的,可以节省时间,但这还不够。你需要严肃、聪明的内存分配器,它们倾向于使用 malloc 或其他方式以块为单位获取内存,并很少释放。这可能是 OP 的教授们想要传达的信息,而他误解了。

1
表现不佳的程序表明,良好的分配器应该为不同大小的块使用单独的池。有了虚拟内存,许多远离的不同池不应该是一个主要问题。如果每个块都舍入到2的幂,并且每个池仅包含这样舍入的块的一个大小,我无法看出碎片化如何会变得非常糟糕。最坏的情况是,程序可能突然对某个大小范围不再感兴趣,留下一些大部分未使用的空池; 我认为这种行为并不常见。 - Marc van Leeuwen
5
需要引用支持声明:精心编写的程序在应用程序关闭之前不会释放内存。 - user565869
12
这个回答读起来像是一连串随意的猜测和假设。有什么东西可以支持这些说法吗? - Chris Hayes
4
我不确定你所说的.NET CLR运行时不会释放任何内存是什么意思。就我测试的结果来看,如果能够释放,它会释放内存。请问您的意思是什么? - Theodoros Chatzigiannakis
1
@vonbrand:GCC有多个分配器,包括自己的垃圾回收器。它在通过过程中消耗内存,并在通过之间进行收集。大多数其他语言的编译器最多只有2个通过,并且在通过之间几乎不释放任何内存。如果您不同意,我很乐意研究您提供的任何示例。 - david.pfx
显示剩余7条评论

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