与其在运行时定期运行垃圾检测,我们为什么不让编译器在适当的地方自动插入free()?这样,我们在编译时只支付一次价格。为什么垃圾收集?为什么不编译器自动插入free()呢?
编译器知道变量超出范围或被重新分配给不同对象的地方。因此,它可以找出对象是否不再可及,并在那里自动插入free()。
不是吗?为什么?
如果是因为多线程,我们可以用单线程/绿色线程语言来完成吗?
与其在运行时定期运行垃圾检测,我们为什么不让编译器在适当的地方自动插入free()?这样,我们在编译时只支付一次价格。为什么垃圾收集?为什么不编译器自动插入free()呢?
编译器知道变量超出范围或被重新分配给不同对象的地方。因此,它可以找出对象是否不再可及,并在那里自动插入free()。
不是吗?为什么?
如果是因为多线程,我们可以用单线程/绿色线程语言来完成吗?
编译器知道变量超出范围或被重新分配给不同对象的地方。
当然可以 - 对于变量。但是你不清楚变量 - 你清除它们指向的内存。只是因为一个变量超出了范围,这并不意味着指向的内存不再可用。
例如:
y = ...
{
x = new X();
if (todayIsTuesday()) {
y = x;
}
} // x just went out of scope
你不能让一个编译时决定存储器是否指向x应在该段的最后一行被释放,因为它取决于的什么日子当它是代码是ran。
因此,要解决这个问题,这个决定必须委托给运行时,通过插入适当的逻辑,例如:
Y* y = ...
{
X* mem = new X();
X* x = mem;
markPointer(&x, mem);
if (todayIsTuesday()) {
y = x;
markPointer(&y, mem);
}
markNoLongerPointer(&x, mem);
} // x just went out of scope
随着markNoLongerPointer()
清除给定为第二参数的存储器,如果它的内部维护的数据结构告诉它,x
是唯一参考那个内存......换句话说,这是引用计数逻辑的粗略起点。
编译器当然可以将这种引用计数逻辑添加到编译后的代码中,有些可以,但正如其他人提到的那样,引用计数有一些缺点:高开销,周期问题,再加上它有时会导致显着的暂停次,当对大数据结构的根的唯一引用超出范围时。有办法解决了这些缺点,虽然,但这是超出范围的这个答案:-)
周期和超出范围情况的根源是为什么例如Delphi不会对容器类型(结构体,类)进行refcounting。它仍然可以使用接口发生,但这些主要用于接口外部对象,而不是内部接口。对于普通类,它使用手动内存管理。 –
正如你可以在这个Wikipedia article on Reference Counting阅读更多有关,也有引用了垃圾收集计数两个主要的缺点:
就从根本上,在不平凡的资源管理情况下,程序员明确有free
内存(和其他资源)无论如何。它可能不完全类似于对free
的呼叫;它可能会从列表中删除对象引用,以便垃圾收集器可以收集它,或将其设置为空引用,或释放智能指针,或从列表中删除对象或对free
进行真正的调用。然而,程序员仍然必须以某种方式明确地指定它,以避免逻辑泄漏。
直到我们有编译器能够开始阅读我们的想法,弄清楚我们正在制作什么类型的软件,或者直到我们开始用完全不同的方式编写代码时,才能在编译期确定它。因为想像一下像Photoshop这样的图像编辑器。何时应该释放图像?当用户关闭它。这对我们人类来说是显而易见的,但它不是信息编译器所具有的。
数字音频工作站的类似情况。音频剪辑何时应该从内存中释放出来?当用户从剪辑编辑器中移除它时,例如这对于人类设计者来说也是显而易见的,但对编译器来说则不是这样我的80年代音乐集何时应该从我的硬盘中删除以节省空间?当我,用户,明确删除它们。直到软件可以开始可靠地开始阅读我的想法,它才不会比手动/明确的更少。
因此,无论您是否有垃圾收集或RAII或其他任何其他用于这些非平凡情况的资源管理都没有。在这种情况下,程序员必须始终明确地使用free
资源来响应正确的输入/事件,而不管它是否是人为的想法,并且设计非常特定于应用程序域,以确定何时应该释放这些资源。
当然,对于一些微不足道的情况,如分配给函数给定范围的内存,编译器可以自动释放它,而不需要任何垃圾回收,而且它们的确如此。即使在C:
void some_func(...)
{
int some_array[64];
...
// some_array's memory will be automatically freed from
// the stack when exiting the function.
}
...并且那里C基本上做你的建议的类比等价物。它会产生增加/减少堆栈指针的指令,例如有效地分配和释放内存,并使释放的内存可供其他地方使用。使用C++,它甚至可以在使用RAII时为堆分配的内存执行此操作,前提是拥有该内存的对象在退出函数的范围时变得无法访问。但是这些类型的临时资源的生命周期与具有堆栈式分配/释放推/拉模式的函数范围相关联,这些都是微不足道的情况。不平凡的情况总是要求程序员以某种方式明确指定何时不再需要资源,因为不平凡的情况处理的持久性状态的生存期不与任何给定函数的范围相关联。
你的意思是像C++ RAII? – Mysticial
你猜对了。它不能。事实上,一些程序员和计算机科学家实际上并不是无能的傻瓜(这里是一个很大的惊喜)。现在为了明白为什么,考虑最简单的垃圾收集形式,引用计数。为什么需要运行时间计数?编译器可以静态计算计数吗?需要怎样的信息才能这样做? –
@ n.m。我认为在没有指针算术语言的情况下是可能的。这个傻瓜肯定是在我身上,因为我无法弄清楚为什么。 – Ron