垃圾收集与非垃圾收集编程语言

因此,如果我理解得很好,垃圾收集会自动释放程序不再使用的对象。 像java中的垃圾收集器。

我听说像C这样的语言不支持垃圾收集,程序可能会有内存泄漏并随后耗尽内存。

那么程序员在不支持垃圾收集的C语言中所犯的错误是什么? 我猜不会在它们不再使用后解除分配对象。 但是由于缺少垃圾收集器,这些是我们可以做的唯一错误吗?

  • 戴上你需要的东西

  • 不解除你不再需要的东西(因为你没有跟踪分配/使用/释放)

  • 重新分配已存在的事物的新实例(不能正确跟踪的副作用)

  • 取消分配你已经释放的东西

  • 取消分配不存在的东西(空指针)

可能还有更多。 重点是:管理内存很棘手,最好使用某种跟踪机制和分配/释放抽象来处理。 因此,您可能会将其内置到您的语言中,因此它可以让您感觉良好和轻松。 手动内存管理不是世界末日 – 它肯定是可行的 – 但是现在,除非您编写实时代码,硬件驱动程序或(可能)可能的最新超优化核心代码游戏,然后手动努力是不值得的,除了作为学术练习。

IMO,垃圾收集语言与非垃圾收集语言中的问题存在互补问题。 对于每个问题,都存在非GC特征错误和GC特征错误 – 非GC程序员责任和GC程序员责任。

GC程序员可能认为他们免除了释放对象的责任,但是对象拥有除内存之外的资源 – 通常需要及时释放的资源,以便可以在其他地方获取 – 例如文件句柄,记录锁,互斥锁。 ..

如果非GC程序员有一个悬空引用(并且通常一个不是bug,因为某些标志或其他状态会将其标记为不使用),GC程序员会有内存泄漏。 因此,非GC程序员负责确保适当地调用free / delete,GC程序员负责确保不需要的引用被清零或以适当的方式处理。

这里声称智能指针不处理垃圾循环。 这不一定是真的 – 有一些引用计数方案可以打破周期并确保及时处理垃圾内存,并且至少有一个Java实现使用(并且仍然可以)引用计数方案,它可以像C ++中的智能指针方案。

参考计数系统中的并发周期收集

当然,通常不会这样做 – 部分原因是你可能只使用GC语言,但也部分使用IMO,因为它会破坏C ++中的关键约定。 你看,许多C ++代码 – 包括标准库 – 在很大程度上依赖于资源分配初始化(RAII)约定,并依赖于可靠和及时的析构函数调用。 在任何处理循环的GC中,你根本就不能拥有它。 当打破垃圾循环时,你无法知道哪个析构函数首先调用而没有任何依赖性问题 – 它甚至可能是不可能的,因为可能存在更多的循环依赖,而不仅仅是内存引用。 解决方案 – 在Java等中,无法保证将调用终结器。 垃圾收集只收集一种非常特殊的垃圾 – 内存。 所有其他资源必须手动清理,因为它们应该是Pascal或C,并且没有可靠的C ++风格析构函数的优势。

最终结果 – 在C ++中进行“自动化”的大量清理必须在Java,C#等手动完成。当然,“自动化”需要引号,因为程序员负责确保为任何堆分配适当地调用删除对象 – 但在GC语言中,有不同但互补的程序员职责。 无论哪种方式,如果程序员未能正确处理这些职责,你就会遇到错误。

[ 编辑 – 有些情况下,Java,C#等显然可靠(如果不一定及时)清理,文件就是这样的一个例子。 这些是不能发生引用循环的对象 – 因为(1)它们根本不包含引用,(2)有一些静态证据表明它包含的引用不能直接或间接地导回到同一类型的另一个对象,或者(3)运行时逻辑确保链而/树/任何可能的循环都不是。 对于资源管理对象而言,情况(1)和(2)非常常见,而不是数据结构节点 – 可能是通用的。 但编译器本身无法合理地保证(3)。 因此,虽然编写最重要的资源类的标准库开发人员可以确保对这些资源类进行可靠的清理,但一般规则仍然是无法保证GC的非内存资源的可靠清理,这可能会影响应用程序定义的资源。 ]

坦率地说,从非GC切换到GC(或反之亦然)不是魔术棒。 它可能会使通常的可疑问题消失,但这只意味着您需要新的技能组合来防止(并调试)一整套新的嫌疑人。

一个优秀的程序员应该通过你们身边的人,并学会处理这两个问题。

那么,你可以犯的错误是:

  • 不要解除你不需要的东西
  • 解除你需要的东西

您可以做出其他错误,但这些错误与垃圾收集有关。

除了丝滑的说法,你还可以双重解除分配。

除了其他注释之外,手动内存管理使某些高性能并发算法更加困难。

一些非GC语言提供称为引用计数智能指针的构造。 这些尝试解决一些问题,例如忘记释放内存或尝试通过自动化某些管理function来访问无效内存。

正如一些人所说,你必须对“智能指针”“聪明”。 智能指针有助于避免一整类问题,但会引入他们自己的一类问题。

许多智能指针可以通过以下方式创建内存泄漏:

  • 循环或循环参考(A指向B,B指向A)。
  • 智能指针实现中的错误(在Boost等成熟库中很少见)
  • 将原始指针与智能指针混合
  • 线程安全
  • 从原始指针不正确地附加或分离

在完全GC环境中不应遇到这些问题。

在C中,您必须在分配了malloc内存上手动调用free 。 虽然这听起来不是那么糟糕,但在处理指向相同数据的单独数据结构(如链接列表)时会变得非常混乱。 您可能最终访问释放的内存或双重释放内存,这两者都会导致错误并可能引入安全漏洞。

另外,在C ++中,你需要注意混合new[]/deletenew/delete[]

例如,内存管理需要程序员确切地知道原因

 const char *getstr() { return "Hello, world!" } 

很好,但是

 const char *getstr() { char x[BUF_SIZE]; fgets(x, BUF_SIZE, stdin); return x; } 

是一件非常糟糕的事情。

另一个常见的错误是在你释放它之后读取或写入内存(内存已经被重新分配并且现在用于其他内容,或者内存尚未被实时分配,因此目前仍由堆管理器拥有)而不是你的申请)。

通常 ,具有垃圾收集的语言限制程序员对内存的访问,并依赖于对象包含的内存模型:

  • 引用计数器 – GC使用它来知道对象何时未使用,以及
  • 类型和大小信息 – 消除缓冲区溢出(并帮助减少其他错误。

与非GC语言相比,模型和限制访问减少/消除了两类错误:

  1. 内存模型错误,例如:

    • 内存泄漏(完成后无法解除分配),
    • 不止一次释放记忆,
    • 释放未分配的内存(如全局或堆栈变量),
  2. 指针错误,例如:

    • 未初始化的指针,以前使用的“遗留”位,
    • 在释放后访问,特别是写入内存( 讨厌!
    • 缓冲区溢出错误,
    • 使用内存作为错误类型(通过强制转换)。

还有更多,但那些是大的。

在谈论垃圾收集时,请不要将OO语言(Java,C#)与非OO语言(C)进行比较。 OO语言(大多数)允许您实现GC(请参阅有关智能指针的注释)。 是的,他们并不容易,但他们帮助很多,而且他们是确定性的。

此外,在考虑除内存以外的资源时,GC语言如何与非GC语言进行比较,例如。 文件,网络连接,数据库连接等…

我想回答这个问题,留给读者,也会对事情有所了解。