因此,如果我理解的话,垃圾收集会自动释放程序不再使用的对象。就像java中的垃圾收集器一样。
我听说像C这样的语言不支持垃圾收集,程序可能会有内存泄漏并随后耗尽内存。
那么程序员在不支持垃圾收集的C语言中所犯的错误是什么?我猜不会在它们不再使用后解除分配对象。但是由于缺少垃圾收集器,这些是我们可以做的唯一错误吗?
答案 0 :(得分:18)
戴上你需要的东西
不解除您不再需要的内容(因为您没有跟踪分配/使用/释放)
重新分配已存在事物的新实例(无法正确跟踪的副作用)
取消分配您已经释放的内容
取消分配不存在的内容(空指针)
可能还有更多。重点是:管理内存很棘手,最好使用某种跟踪机制和分配/释放抽象来处理。因此,您可能会将其内置到您的语言中,因此它可以让您感觉良好和轻松。手动内存管理不是世界末日 - 它肯定是可行的 - 但是现在,除非您正在编写实时代码,硬件驱动程序或(可能)可能的最新超优化核心代码游戏,然后手动努力是不值得的,除了作为学术练习。
答案 1 :(得分:13)
IMO,垃圾收集语言与非垃圾收集语言的语言存在互补问题。对于每个问题,都存在非GC特征错误和GC特征错误 - 非GC程序员责任和GC程序员责任。
GC程序员可能认为他们免除了释放对象的责任,但是对象拥有除了内存以外的资源 - 通常需要及时释放资源以便可以在其他地方获取 - 例如文件句柄,记录锁,互斥锁...如果非GC程序员有一个悬空引用(并且通常一个不是bug,因为某些标志或其他状态会将其标记为不使用),GC程序员会发生内存泄漏。因此,非GC程序员负责确保正确调用free / delete,GC程序员负责确保不需要的引用被清零或以适当的方式处理。
这里声称智能指针不处理垃圾循环。这不一定是真的 - 有一些引用计数方案可以打破周期并确保及时处理垃圾内存,并且至少有一个Java实现使用(并且仍然可以)引用计数方案,这种方案可以很容易地实现为C ++中的智能指针方案。
Concurrent Cycle Collection in Reference Counted Systems
当然这通常不会 - 部分是因为你可能只使用GC语言,但也部分是IMO,因为它会破坏C ++中的关键约定。你看,许多C ++代码 - 包括标准库 - 在很大程度上依赖于资源分配初始化(RAII)约定,并依赖于可靠和及时的析构函数调用。在任何处理循环的GC中,你根本就不能拥有它。当打破垃圾循环时,你无法知道哪个析构函数首先调用而没有任何依赖性问题 - 它甚至可能是不可能的,因为可能存在更多的循环依赖,而不仅仅是内存引用。解决方案 - 在Java等中,无法保证将调用终结器。垃圾收集只收集一种非常特殊的垃圾 - 内存。所有其他资源必须手动清理,因为它们应该是Pascal或C,并且没有可靠的C ++风格析构函数的优势。
最终结果 - 在C ++中进行“自动化”的大量清理工作必须在Java,C#等手动完成。当然,“自动化”需要引号,因为程序员负责确保为任何人正确调用删除堆分配的对象 - 但在GC语言中,有不同但互补的程序员职责。无论哪种方式,如果程序员未能正确处理这些职责,你就会遇到错误。
[ EDIT - 有些情况下,Java,C#等显然可靠(如果不一定及时)清理,文件就是这样的一个例子。这些是不能发生引用循环的对象 - 因为(1)它们根本不包含引用,(2)有一些静态证据表明它包含的引用不能直接或间接地导回到同一类型的另一个对象,或者(3)运行时逻辑确保尽管链/树/任何可能的循环都不是。对于资源管理对象而言,情况(1)和(2)非常常见,而不是数据结构节点 - 可能是通用的。但编译器本身无法合理地保证(3)。因此,虽然编写最重要的资源类的标准库开发人员可以确保为那些人提供可靠的清理,但一般规则仍然是无法保证GC的非内存资源的可靠清理,这可能会影响应用程序定义的资源。 ]
坦率地说,从非GC切换到GC(反之亦然)不是魔术棒。它可能会使通常的可疑问题消失,但这只意味着您需要新的技能组合来防止(并调试)一整套新的嫌疑人。
一个优秀的程序员应该超越你所在的人,并学会处理这两个。
答案 2 :(得分:7)
嗯,您可以犯的错误是:
您可以做出其他错误,但这些错误与特定与垃圾回收有关。
答案 3 :(得分:3)
除了丝滑的说法,你还可以双重解除分配。
答案 4 :(得分:2)
在C中,您必须在分配有free
的内存上手动调用malloc
。虽然这听起来不是那么糟糕,但在处理指向相同数据的单独数据结构(如链接列表)时,它可能会非常非常混乱。您可能最终访问释放的内存或双重释放内存,这两者都会导致错误并可能引入安全漏洞。
此外,在C ++中,您需要注意混合new[]/delete
and new/delete[]
。
例如,内存管理需要程序员确切地知道原因
const char *getstr() { return "Hello, world!" }
很好但是
const char *getstr() {
char x[BUF_SIZE];
fgets(x, BUF_SIZE, stdin);
return x;
}
是一件非常糟糕的事情。
答案 5 :(得分:2)
除了其他注释之外,手动内存管理使某些高性能并发算法更加困难。
答案 6 :(得分:2)
一些非GC语言提供称为引用计数智能指针的构造。这些试图解决一些问题,例如忘记释放内存或尝试通过自动化某些管理功能来访问无效内存。
正如一些人所说,你必须对“智能指针”“聪明”。智能指针有助于避免一系列问题,但会引入他们自己的一类问题。
许多智能指针可以通过以下方式创建内存泄漏:
在完全GC环境中不应遇到这些问题。
答案 7 :(得分:0)
另一个常见的错误是在你释放它之后读取或写入内存(已经重新分配并且现在用于其他内容的内存,或者尚未被实例化的内存,因此目前仍然由堆管理器,而不是您的应用程序)。
答案 8 :(得分:0)
通常,带有垃圾收集的语言限制了程序员对内存的访问,并依赖于对象包含的内存模型:
与非GC语言相比,模型减少/消除了两类错误:限制访问:
内存模型错误,例如:
指针错误,例如:
还有更多,但那些是大的。
答案 9 :(得分:-3)
在谈论垃圾收集时,请不要将OO语言(Java,C#)与非OO语言(C)进行比较。 OO语言(大多数)允许您实现GC(请参阅有关智能指针的注释)。是的,他们并不容易,但他们帮助很多,而且他们是确定性的。
此外,在考虑内存以外的资源时,GC语言与非GC语言的比较如何。文件,网络连接,数据库连接等......
我认为回答这个问题,留给读者,也会对事情有所了解。