为什么要使用垃圾收集器?

时间:2010-06-29 14:39:27

标签: c garbage-collection

  

可能重复:
  Garbage Collection in C++ — why?

嗨,我读过几篇关于垃圾收集器的文章,还有一件事我不明白 - 为什么要使用垃圾收集?

我会尝试解释我的想法:

垃圾收集器应该在不需要的情况下将动态分配的内存释放回系统,对吧?所以,如果你用C语言编写程序,你就知道你需要一些内存,所以如果没有,你可以简单地销毁它。

那么,为什么要使用GC,当你需要做的只是明智的内存分配/释放?或者我错过了什么?感谢。

22 个答案:

答案 0 :(得分:19)

提高工作效率。换句话说,程序员可以专注于编写对他的特定问题唯一的位。

答案 1 :(得分:13)

避免错误。无论你如何谨慎地解除内存释放,你最终都会犯错误,或者你最终会编写一个需要复杂内存参考模式的程序,这会使错误的可能性大得多。

任何给定足够时间存在的可能性都将成为现实,最终你会用手动方法泄漏内存,除非特别注意监视内存消耗。这种额外的努力会浪费时间从编码到程序的主要目的,这可能不是为了管理内存。

此外,即使您的程序没有泄漏内存,垃圾收集通常也会比许多非垃圾收集方法更有效地处理内存。大多数人没有new个对象块来避免多次new调用,之后他们也不会重新访问和清理未使用的new ed对象的缓存。大多数手动垃圾收集方法都集中在块边界释放内存,这可能会让垃圾徘徊的时间比它需要的时间长。

您在手动垃圾收集上添加的每个附加好处和功能都会使您更接近自动垃圾收集。使用没有实用程序来收集手动调用之外的垃圾来释放它将无法轻松扩展。您要花费大量时间检查内存分配/重新调整,否则您将不会花费足够的时间来避免内存泄漏。

无论哪种方式,自动垃圾收集都会为您解决这个问题,让您回到程序的主要部分。

答案 2 :(得分:10)

因为我们不再生活在80年代初了。这是浪费开发人员的时间,当您即将创建一个惊人的应用程序时,关心最低级别的任务是非常烦人的。

答案 3 :(得分:8)

因为我们不够聪明。

答案 4 :(得分:8)

当我编写程序时,我喜欢专注于我的问题领域,而不是关注不相关的实现细节的细节。例如,如果我正在编写Web服务器,我的问题域是网络连接,协议,数据传输/接收等,而不是内存分配和释放。如果我正在编写一个视频游戏,我的问题域是图形性能,也许是AI,而且不再是内存分配和释放。

任何时候我花在处理不属于我的问题域的事情上都浪费了时间,可能会浪费在我的问题域上。通过专注于低级细节,换句话说,我实际工作的质量 - 我实际上试图解决的问题 - 遭受了损失。

此外,你的“所有你需要做的事情实际上只是明智的内存分配/释放”这一点只能突出两种可能性(我能想到,无论如何):

  1. 你很缺乏经验。
  2. 你可能 - 潜在地下意识地 - 削弱了你的设计,使它们不受你对内存管理的简单假设的限制。
  3. 真实世界软件中的内存管理是一种明显的 -trivial努力。任何大小的现代软件中典型的复杂数据结构在确定任何给定的动态分配存储器的“活性”和“所有权”方面导致令人难以置信的混淆。随着线程的引入,或者更糟糕的是,使用任何形式的共享状态进行多处理(对称和其他),这会变得更加复杂(可能是数量级)。

    在非托管内存环境中编写的软件中最常见的错误与管理不善的动态分配内存有关并非偶然。

    那么为什么要使用垃圾收集?因为在处理动态分配的内存的变幻莫测时,你并不像你想象的那么聪明。真的,你不是。不管你认为自己有多聪明。如果您认识到自己并不聪明,那么您的设计就会瘫痪,以保持您的内存管理简单易懂。但是,如果你傲慢地相信你可以处理任何事情,那么你只需要搞砸那些必须处理你的蹩脚,易崩溃和/或内存食用软件的用户。

答案 5 :(得分:5)

考虑两个独立子系统使用特定指针的情况。一个子系统可以用变量来完成,程序员可能会想,“我已经完成了这个,我将继续并释放它”,完全没有意识到另一个子系统仍然需要访问它。或者另一个陷阱,开发人员认为,“我不确定是否有另一个可能需要这个的子系统”(即使没有)导致内存泄漏。这种情况在复杂的系统中出现了很多。

答案 6 :(得分:4)

我同意mouviciel的评论。但垃圾收集器确实允许更快的开发,因为开发人员不再需要担心内存泄漏,允许他们专注于他们程序的其他方面。

但请注意,如果您使用具有垃圾收集的语言进行编程,那么了解这一事实是非常明智的。它几乎是必须(IMO)了解它是如何工作的,以及它在后台做了什么。

答案 7 :(得分:4)

这是一种反哑的程序员机制。相信我,当代码变得非常复杂时,在考虑动态分配的内存时,我们都同样愚蠢。

在我作为程序员的短暂经历中,我花了(累积)试图弄清楚为什么valgrind(或其他类似工具)报告内存泄漏,当所有内容都被“明智地编码”时。

答案 8 :(得分:3)

现在,大多数使用垃圾收集器的人都是在托管环境(如Java虚拟机或.NET公共语言运行时)中这样做的。这些托管环境增加了额外的皱纹:它们限制了指向事物的能力。例如,在CLR中,有一个指针的概念(您可以通过托管IntPtr或非托管unsafe代码块使用),但是在允许使用的条件有限的情况下他们。在大多数情况下,您必须“固定”内存中的相应对象,以便GC在您使用指针时不会移动它们。

为什么这很重要?因为事实证明,允许更新指针并在内存中移动对象的托管分配器可以malloc样式分配器更高效。你可以做一些很酷的事情,比如generational garbage collection,它使堆分配和堆栈分配一样快,你可以更容易地分析应用程序的内存行为,哦,是的,你也可以轻松检测未引用的对象并自动释放它们。

所以这不仅仅是提高程序员工作效率的问题(尽管如果你问任何使用托管语言工作的人,他们会证明他们提供的生产力提高了),这也是一个问题启用全新的编程技术。

最后,当使用函数式编程语言(或函数式编程)时,垃圾收集变为真正的必要。实际上,第一个垃圾收集器是麦卡锡于1959年发明的,是Lisp语言开发的一部分。原因有两个:第一,函数式编程鼓励不可变数据结构,更容易收集,其次,在纯函数式编程中没有分配函数;内存总是被分配为“堆栈”(函数本地),然后移动到“堆”,如果它被closure捕获。 (这是一个粗略的过度简化,但有助于说明这一点。)

所以......如果你是以强制性的方式进行编程,并且你“足够聪明”来做正确的事情,你的所有内存分配,你都不需要垃圾收集。但是如果你想改变编程风格以利用编程技术的最新进展,你可能会对使用垃圾收集器感兴趣。

答案 9 :(得分:2)

在复杂项目中处理多次调用库和未编写的外部代码时,很难跟踪需要释放的对象以及外部库和代码中其他位置释放的对象

现在有很多工具可以使追踪内存泄漏的任务变得更容易,但它们往往是潜伏的错误,只有在系统运行数小时或数天后才会变得明显。

但是,我同意你的观点。如果我可以控制代码库,我更喜欢写一些我负责的东西(比如c)。但是,如果我必须与外部力量合作,那么拥有体面的垃圾收集器的东西就会更具吸引力。

答案 10 :(得分:2)

  

所以,如果你用C语言编写程序,你就知道你需要一些内存,所以如果没有,你可以简单地销毁它。

至少这是理论。问题是它可以使代码复杂化。例如,这个:

for (x in ComputeBigList()) ...

变成这个

var xs = ComputeBigList();

try {
   for(x in xs) ...
} finally {
   FreeMemory(xs);
}

缺少垃圾收集器要求我们命名ComputeBigList的结果,将其存储在变量中,然后添加包含在finally中的删除语句,以确保它实际上已被删除

这是C ++粉丝应该指出C ++保证的析构函数调用可以使这更容易的地方。也就是说,假设您希望对象能够逃脱创建它们的动态范围,那么您将获得与引用计数等相关的开销和其他代码。 (即:我分配一个对象,然后将其返回。)

GC做的另一件事是有用的是控制你如何使用你的记忆。重定位GC允许您安排对象,以便更有效地访问它们。 GC通常会为您的运行时提供更多的灵活性,以支付回收内存的价格。 (显式frees和refcount更新必须立即进行。)

答案 11 :(得分:2)

如果您不首先生产垃圾,则不需要垃圾收集。

避免垃圾的一种方法是根本不使用动态内存分配。大多数嵌入式程序不使用动态内存分配。即使使用动态内存分配(即使在许多PC程序中),也常常没有理由使用它。 (仅仅因为动态内存分配是可能的并不意味着它应该在任何地方使用。)

避免垃圾收集的另一种方法是使用不将引用内容分开的语言。在这种情况下,实际的内存泄漏甚至是不可能的。 (但当然仍然可以使用太多内存。)恕我直言,高级语言不应该混淆“指针”(地址变量)。

答案 12 :(得分:2)

释放不再需要的内存 是一个理想的目标,但不可能在所有普遍性中自动执行。即使没有外部输入(这可能会影响是否需要某些数据),在给定内存和完整代码的完整状态的情况下,决定是否需要一些内存等同于{ {3}},这是计算机无法解决的问题。

毋庸置疑,随着应用程序规模的扩大,同样的问题也会超出普通程序员大脑的容量。在实践中,只有在两种情况下才能实现完美正确的内存管理:

  1. 问题很简单(例如,短命令命令行应用程序),程序员足够自律;
  2. 程序员是halting problem
  3. 在所有其他情况下,我们必须使用近似值。垃圾收集器依赖于以下近似值:它检测无法访问的内存块。它无法判断是否使用可访问的块,但是将使用无法访问的块(因为使用意味着到达)。另一个常见的近似(许多程序员使用它们认为它们足够聪明)就是简单地假设他们想到了每个块,然后祈祷最好(一个变体是:教育你的用户相信内存泄漏是一个特征,并且偶尔重启是正常的。)

答案 13 :(得分:1)

  

那么,为什么要使用GC,当你需要做的只是明智的内存分配/释放时呢?

问题在于非常难以充分明智。智慧赌注失败,你得到内存泄漏或崩溃。以下是自动内存管理中计算机应用智慧的快速入门指南。

如果你有一个简单的程序(零复杂度),你可以使用基于堆栈的分配来处理所有事情。以这种方式获取内存分配非常容易,但它也是一个非常有限的计算模型(并且你也遇到堆栈空间问题)。所以你开始使用堆;这就是“乐趣”开始的地方。

第一级复杂性是你有指针,其生命周期绑定到堆栈帧。同样,这很简单,并构成了大量C ++编程的基础。

第二级复杂性是你有引用计数的地方。这是C ++智能指针的基础,并且非常善于处理几乎所有直到非有向无环图的森林。你可以用这个来实现很多,它允许一些计算模型在函数式编程和并行编程中也能很好地工作。

除此之外是第三级垃圾收集。这可以处理内存结构的任意图形,但需要花费更多的内存(因为你通常不会尽快解除分配)。主要成本之一是分配的内存量往往更大,因为它只是在您可以删除内存之后才真正有资格进行自动删除的事实,你是否足够聪明终身。

答案 14 :(得分:1)

  

你知道你需要一些记忆,所以如果没有,你可以简单地摧毁它。

您可以使用类似的参数来证明任何省力设备。为什么在只生成汇编语言时编写数学表达式?为什么在使用二进制文件时可以使用可读字符?

原因很简单。我和那些在他们领域中表现最好的程序员一起工作。我可以毫不夸张地说,他们中的一些人已经在他们的领域上写了这本书。然而,这些人正在用C ++编程,并在内存管理方面犯错误。当他们犯这些错误时,他们特别难以找到和纠正。为什么有惊人的人才可以将他们的才能引向别处,浪费他们的时间做一些机器可以做得更好的事情?

(是的,这个问题有很好的答案。例如,当你的计数中的每一个字节都没有,所以你不能随时得到任何垃圾。但事实并非如此。)

答案 15 :(得分:1)

垃圾收集可以更有效。

要分配内存,malloc需要摆弄以找到足够大的连续内存范围。使用压缩垃圾收集器,分配内存会使指针碰撞(或靠近它)

在C ++中,您可以通过使用智能指针并严格遵守约定,在没有垃圾收集器的情况下安全,干净地处理内存。但是(1)这在所有情况下都不起作用,即使使用shared_ptr和weak_ptr,以及(2)引用计数需要跨线程协调,这会降低性能。

可用性是一个更重要的问题,但垃圾收集有时比确定性地释放内存更快。

答案 16 :(得分:1)

如果您不需要进行实时应用程序(如果强迫他,您无法确定垃圾收集器何时会执行其工作事件),或者当您不介意完全控制记忆时,可以开发头部,几乎可以确保不会造成内存泄漏。

答案 17 :(得分:0)

如果没有垃圾收集器,无论何时动态分配内容,都必须跟踪何时不再需要它,并且仅在之后不再需要它时将其销毁。这可能很困难,尤其是当程序 all 的许多不同部分都有指向一个对象的指针时,并且没有人知道其他代码可能正在使用它。

无论如何,这就是理论。实际上,我不得不承认它对我来说也没那么成功。特别是,当(大多数)人们意识到他们的代码将使用垃圾收集器时,他们倾向于将内存管理视为不是问题,甚至根本不考虑问题。因此,他们可以快速进入并开始编码。对于他们在开始之前很清楚的小问题,这可能是一个重大的胜利 - 但对于更大的问题,似乎(至少对我来说)倾向于在他们真正理解问题之前跳入并开始编写代码。

根据我的经验,缺少垃圾收集器会让开发人员更多地考虑前期的生命周期问题。在这个过程中,他们有动力找到简单的解决方案来解决对象的生命周期问题 - 他们通常会这样做。在这个过程中,他们通常简化了代码(不仅仅是内存管理),以至于它更简单,更清晰,更易理解。

在某种程度上,它让我想起了很多旧parable of two programmers。在项目结束时,查看使用垃圾收集的代码的经理认为这是他们使用垃圾收集的真正好事。这个问题显然比他们意识到的要复杂得多,而且考虑到代码的复杂性和生命周期问题,任何人都可以手动跟踪它们并生成甚至接近泄漏的代码 no -free。

在没有垃圾收集的情况下做同样的事情,反应恰恰相反。他们意识到问题(总的来说)实际上比他们意识到的要简单得多。对象生命周期问题实际上并不像他们预期的那么复杂,并且生成无泄漏代码并不是特别具有挑战性。

答案 18 :(得分:0)

您可以尽快释放Interop资源(锁定文件)。 Gc.Collect可以确保释放COM对象(如果没有引用)。

如果您执行PrintPreview,则每个页面(图像+图元文件)需要2个Gdi句柄。 这些资源不会被PrintDocument或PrintControler释放,等待GC。

我在交互式程序上测试过,当用户返回主菜单时使用Gc.Collect。 通过此操作,任务管理器的报告内存大约为50%。

我认为这不重要,但是当你知道没有引用大量内存时,代码是Gc.Collect是一个简单的选择。

答案 19 :(得分:0)

如您所述,您可以自己进行垃圾收集。添加垃圾收集器只会让您不必担心它,也不必花时间编写和测试垃圾收集代码。如果您正在处理包含内存泄漏的现有代码库,那么使用自动垃圾收集器可能比尝试详细了解所有现有代码以查找和修复问题更容易(也更有效)。

话虽这么说,我不喜欢将自动垃圾收集工具添加到没有内置的语言中。如果设计的语言是假设开发人员会考虑内存分配和解除分配,然后(恕我直言)它使开发人员不利于从他们那里删除这个责任。无法准确控制何时以及如何释放内存可能导致行为不一致。思考和定义动态分配内存的完整生命周期是规划代码的重要部分。没有自动化系统真正替代仔细和准确的编程(这不仅仅适用于垃圾收集器)。

答案 20 :(得分:0)

内存管理是实施问题,它与程序的目标无关。

按程序的目标我的意思是商业逻辑。

当您处理实施问题时 - 您会花费时间和精力来处理无法完成程序的事情。

答案 21 :(得分:0)

你只需要聪明,这是对的;) 但是,如果您的设计不正确,可以很容易地监督某些事情。

使用垃圾收集但是你不必关心内存并且可以更多地关注你的程序的其余部分,因此可能开发“更快”