代码之家  ›  专栏  ›  技术社区  ›  B.Gen.Jack.O.Neill

为什么要使用垃圾收集器?[复制品]

  •  18
  • B.Gen.Jack.O.Neill  · 技术社区  · 15 年前

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

    嗨,我读了一些关于垃圾收集器的文章,但有一件事我还是不明白——为什么要使用垃圾收集?

    我会尽力解释我的想法:

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

    那么,为什么要使用gc,而实际上您所需要做的只是明智地分配内存/释放内存?还是我错过了什么?谢谢。

    22 回复  |  直到 14 年前
        1
  •  19
  •   Martin Wickman    15 年前

    提高生产效率。换言之,程序员可以专注于为自己的特定问题编写独特的位。

        2
  •  13
  •   Edwin Buck    15 年前

    以避免错误。无论您多么小心地释放内存,要么最终会出错,要么最终编写一个需要复杂内存引用模式的程序,这将使出错的可能性更大。

    在给定的足够时间内存在的任何可能性都将成为现实,最终您将使用手动方法泄漏内存,除非将额外的工作专门用于监视内存消耗。这项额外的工作将时间从编码缩短到程序的主要目的,这可能不是为了管理内存。

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

    您堆积在手动垃圾收集上的每一个附加的好处和特性都将使您更接近自动垃圾收集。除了手动调用释放垃圾之外,不使用任何实用程序来收集垃圾不会很容易扩展。要么你会花费大量的时间检查内存分配/重设,要么你不会花费足够的时间来避免内存泄漏。

    无论采用哪种方法,自动垃圾收集都可以为您解决这个问题,使您能够回到程序的主要点。

        3
  •  10
  •   Turing Complete    15 年前

    因为我们已经不在80年代初了。这是浪费开发人员的时间,当您要创建一个令人惊奇的应用程序时,只关心最底层的任务是很烦人的。

        4
  •  8
  •   swegi    15 年前

    因为我们不够聪明。

        5
  •  8
  •   JUST MY correct OPINION    15 年前

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

    任何时候我花在不属于我的问题领域的事情上的时间都是浪费在我的问题领域上的时间。通过专注于低层次的细节,换句话说,我实际工作的质量——我实际上试图解决的问题——受到了影响。

    此外,您的“您所需要做的只是明智地分配内存/解除分配”位仅用于突出两种可能性(我可以想到,无论如何):

    1. 你很缺乏经验。
    2. 你已经——很可能是下意识地——削弱了你的设计,使它们保持在你关于记忆管理的简单假设的限制范围内。

    现实软件中的内存管理无疑是 -微不足道的努力。复杂的数据结构——任何一个相当大的现代软件的典型特征——在决定任何一个给定的动态分配内存的“活跃性”和“所有权”方面,都会导致令人难以置信的混淆。通过引入线程,或者更糟的是,采用任何形式的共享状态的多处理(包括对称处理和其他处理),这就变得更加复杂(可能按数量级)。

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

    那么,为什么要使用垃圾收集呢?因为在处理动态分配内存的变幻莫测时,您并不像您想象的那么聪明。真的,你不是。不管你觉得自己有多聪明。如果你意识到自己不够聪明,你就会破坏你的设计,使你的内存管理变得足够简单,以至于你无法理解。然而,如果你傲慢地相信你可以处理任何事情,你只会让那些不得不处理你的蹩脚的、易崩溃的和/或内存消耗软件的用户上当。

        6
  •  5
  •   Nick Van Brunt    15 年前

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

        7
  •  4
  •   Meiscooldude    15 年前

    我同意莫维吉尔的评论。但是垃圾收集器确实允许更快的开发,因为开发人员不再需要担心内存泄漏,从而可以将精力集中在程序的其他方面。

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

        8
  •  4
  •   Andrei Ciobanu    15 年前

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

    在我作为一名程序员的短暂经历中,我花了(累积的)几天时间试图弄明白为什么Valgrind(或其他类似工具)在报告内存泄漏时,所有的东西都是如此“明智地编码”。

        9
  •  3
  •   Daniel Pryden    15 年前

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

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

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

    最后,垃圾收集变得真正 必要的 使用时 函数式编程 语言(或功能风格的编程)。事实上,第一个垃圾收集器是麦卡锡在1959年发明的,作为Lisp语言开发的一部分。原因有两方面:第一,函数式编程鼓励不可变的数据结构,这更容易收集;第二,在纯函数式编程中,没有分配函数;内存总是被分配为“堆栈”(函数局部变量),如果被 closure . (这是一种过于简单化的说法,但可以用来说明这一点。)

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

        10
  •  2
  •   Adam Shiemke    15 年前

    在使用多个对库的调用和未编写的外部代码的复杂项目中工作时,很难跟踪需要释放的对象以及由外部libs和代码中的其他位置释放的对象。

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

    不过,我同意你的看法。如果我可以控制代码库,我更喜欢在我负责的地方写一些东西(比如C)。但是如果我必须与外部力量合作,一个像样的垃圾收集器的东西更吸引人。

        11
  •  2
  •   mschaef    15 年前

    所以,如果你用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更新总是必须立即进行。)

        12
  •  2
  •   PauliL    15 年前

    如果一开始不产生垃圾,就不需要收集垃圾。

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

    另一种避免垃圾收集的方法是使用不分离的语言 参考 内容 . 在这种情况下,实际的内存泄漏甚至是不可能的。(当然,仍然有可能使用太多的内存。)imho,高级语言根本不应该与“指针”(地址变量)混淆。

        13
  •  2
  •   Thomas Pornin    15 年前

    释放内存 不再需要了 是一个理想的目标,但不可能在所有的一般性中自动完成。即使没有外部输入(这可能影响是否需要某些数据),在给定内存和完整代码的完整状态下,决定是否需要某些内存相当于 halting problem 这对于计算机来说是不可能解决的。

    不用说,随着应用程序规模的增长,同样的问题也很快超过了普通程序员大脑的能力。实际上,只有在两种情况下才能实现完全正确的内存管理:

    1. 问题很简单(例如,短时间的命令行应用程序),程序员纪律严明;
    2. 程序员是 Donald Knuth .

    在所有其他情况下,我们必须使用近似值。垃圾收集器依赖于以下近似值:它检测不可访问的内存块。它无法判断是否将使用可访问块,但无法访问的块将 被使用(因为使用意味着达到)。另一种常见的近似方法(许多认为自己足够聪明的程序员使用)是简单地假设他们想到了每个块,然后祈祷最好(一种变体是:教育用户相信内存泄漏是一种特性,并且时不时地重新启动是正常的)。

        14
  •  1
  •   Robert Cooper    15 年前

    垃圾收集可以更有效。

    要分配内存,malloc需要四处走动,以找到足够大的连续内存跨度。使用压缩垃圾收集器,分配内存会碰到指针(或接近指针)。

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

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

        15
  •  1
  •   sigfpe    15 年前

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

    你可以用类似的论据来证明任何省力装置的合理性。当你能生成汇编语言时,为什么还要写数学表达式呢?为什么可以使用二进制时使用可读字符?

    原因很简单。我和程序员一起工作,他们是他们领域中最优秀的一些人。我可以毫不夸张地说,他们中的一些人已经在自己的领域写了这本书。然而,这些人正在C++编程,并在内存管理方面出错。当他们犯这些错误时,特别是很难发现和纠正。为什么那些才华横溢的人会浪费时间去做机器能做得更好的事情呢?

    (是的,这个问题有很好的答案。例如,当您的内存中的每一个字节都计数时,您就不能在任何时候拥有任何垃圾。但一般情况并非如此。)

        16
  •  1
  •   Ephemere    15 年前

    当你不需要做实时应用程序(如果你强迫垃圾收集器,你不能确定垃圾收集器什么时候会做他的工作事件)或者当你不介意完全控制你的内存时,你可以开发头部自由,几乎可以确保不会发生内存泄漏。

        17
  •  1
  •   Donal Fellows    14 年前

    那么,为什么要使用gc,而实际上您所需要做的只是明智地分配内存/释放内存?

    问题是,要有足够的智慧变得异常困难。在智慧赌注上失败,你会得到一个内存泄漏或崩溃。以下是计算机在自动内存管理中应用智慧的快速封装指南。

    如果您有一个简单的程序(复杂性的零级),您可以使用基于堆栈的分配来处理所有事情。通过这种方式很容易获得内存分配,但它也是一个非常受限的计算模型(您也会遇到堆栈空间问题)。所以您开始使用堆;这就是__fun__开始的地方。

    复杂度的第一个层次是指针的生命周期绑定到堆栈帧。同样,这是相当简单的,并形成了许多C++编程的基础。

    第二个复杂度级别是进行引用计数的地方。这是C++智能指针的基础,在处理有向无圈图的森林中几乎都是很好的。您可以用它实现很多功能,它允许一些计算模型与函数编程和并行编程一起工作得相当好。

    除此之外,还有第三层,垃圾收集。这可以处理任意的内存结构图,但是需要花费更多的内存(因为一般情况下,您不会很快取消分配)。其中一个主要的成本是,分配的内存量往往更大,因为只有在删除了内存之后,它才真正符合自动删除的条件,如果你足够聪明,能够计算出生命周期的话。

        18
  •  0
  •   Tapdingo    15 年前

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

    但是,使用垃圾收集,您不必关心内存,可以将更多精力集中在程序的其余部分,从而可能开发“更快”

        19
  •  0
  •   bta    15 年前

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

    也就是说,我不喜欢在没有内置垃圾收集功能的语言中添加自动垃圾收集功能。如果语言的设计假定开发人员会考虑内存分配和取消分配,那么(imho)将这一责任从开发人员身上移除会对开发人员造成损害。如果不能精确控制释放内存的时间和方式,可能会导致行为不一致。思考和定义动态分配内存的完整生命周期是计划代码的重要部分。没有一个自动化系统可以真正代替精心而准确的编程(这不仅仅适用于垃圾收集器)。

        20
  •  0
  •   Jerry Coffin    15 年前

    如果没有垃圾收集器,任何时候动态地分配某个东西,都必须跟踪何时不再需要它,并只销毁它。 之后 你不再需要它了。这可能很困难,尤其是当/如果程序的许多不同部分 全部的 有指向一个对象的指针,但没有人知道其他代码可能正在使用它。

    这就是理论。事实上,我不得不承认,我也没有这样做。尤其是,当(大多数)人意识到他们的代码将使用垃圾收集器时,他们往往会将内存管理视为根本不存在问题,甚至根本不需要考虑问题。因此,它们可以更快地跳入并开始编码。对于他们在开始之前很好地理解的小问题,这可能是一个重大的胜利——但是对于更大的问题,似乎(至少对我来说)倾向于在他们真正理解问题之前就开始编写代码。

    根据我的经验,缺少垃圾收集器会让开发人员提前考虑更多的生命周期问题。在这个过程中,他们被激励去寻找 简单的 对象生存期问题的解决方案——它们通常都是这样做的。在这个过程中,他们通常会简化代码(不仅仅是内存管理),使其变得更简单、更清晰、更易于理解。

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

    在没有垃圾收集的情况下做同样的事情的最后,反应是相反的。他们意识到这个问题(一般来说)比他们意识到的要简单得多。对象生存期问题并不像他们预期的那么复杂,生成无泄漏代码一点也不具有挑战性。

        21
  •  0
  •   x77    15 年前

    只要有可能(锁定的文件),就需要释放interop资源。 collect可以确保释放COM对象(如果未引用)。

    如果您进行打印预览,则每个页面(图像+元文件)都需要2个GDI句柄。 这些资源是由打印文档或打印控制程序发布的,正在等待GC。

    我测试了一个交互程序,当用户返回主菜单时使用gc.collect。 使用此操作,任务管理器的报告内存约为50%。

    我认为这并不重要,但是编写一个gc.collect代码,当你知道很多内存没有被引用时,这是一个简单的选择。

        22
  •  0
  •   Avram    14 年前

    内存管理是实现问题 这与项目目标无关。

    我指的是程序的目标,如业务逻辑。

    当你处理实现问题时——你把你的时间和精力都投入到那些不能帮助你完成项目的事情上。