为什么Ada没有垃圾回收器?

15

我知道在Ada开发时期,GC并不受欢迎,而且对于嵌入式编程的主要用例,它仍然不是一个好的选择。

但考虑到Ada是一种通用编程语言,为什么后续版本的语言和编译器实现中没有引入部分和可选的(只跟踪显式标记内存对象)垃圾收集器呢?

我简直无法想象开发一个正常的桌面应用程序而没有垃圾收集器。


5
问题陈述有些错误,因为某些情况下确实会使用GC,并且它是任何编译器编写者可以选择的选项。我希望搜索Ada语言的人不会看到这个问题并认为Ada不允许使用GC。 - T.E.D.
它不是一种通用的编程语言。它仅被设计和强制用于美国国防部项目。非常独特的领域。 - user207421
7个回答

35

Ada的设计初衷是用于军事应用。其设计中的一个重点是确定性,即希望Ada程序在任何环境、所有操作系统下都能够始终如一地运行。

垃圾回收器会将一个应用程序变成两个互相对抗的程序。Java程序会在GC决定工作时随机出现卡顿,如果GC处理得过慢,有时会发生内存不足而有时不会。

简而言之:垃圾收集器引入了程序设计者不希望出现的一些变量。制造混乱--清理它!同样的代码,每次都表现相同。

并不是说Ada成为了全球疯狂的成功。


好的,但是为什么经过两次重大语言修订后,Ada仍然没有垃圾回收器呢?甚至没有一个可选的?C++之所以没有垃圾回收器,只有一个原因——没有足够的时间正确地指定这些东西。 - Lothar
8
语言中并没有像你所暗示的那样表达“没有垃圾回收器”的意思。如果有人想在他们的编译器中加入一个垃圾回收器,他们可以自由添加。一些编译器已经做到了。 - T.E.D.
3
抱歉,这个答案完全是错误的,例如,在Ada的任务模型中,有很大的部分是非确定性的。Ada语言规范中没有任何阻止GC的内容 - 如果需要,可以使用GC可选的附加包。 - YermoungDer
2
值得注意的是,C语言的malloc在需要将小块内存重新组合成大块内存时,也有类似于Java(和其他语言)垃圾回收器的“抽搐”模式,以类似于非确定性的方式进行。分配/释放的便利性总是要付出代价的。 - Darkhogg
我们可以说,在C/C++中的malloc并不是一个打嗝。我们成功地在正确的时间做到了这一点。就像讲话、呼吸和喝水一样,都需要掌握好时机和管理。更不用说,在喝水时变成打嗝了。这关乎时机和管理。 - NOTSermsak

16
因为Ada是为实时控制武器的国防系统设计的,垃圾回收会干扰应用程序的时间。这很危险,因此多年来,Java都附带着警告,不要将其用于医疗保健和军事控制系统。
我认为,Java不再具有此类免责声明的原因是底层硬件变得更快,同时Java具有更好的GC算法和更好的GC控制能力。
请记住,Ada是在20世纪70年代和80年代开发的,在那个时候计算机比今天弱得多,控制应用程序的时间问题至关重要。

1
我还会添加航空电子软件:没有人希望在尝试着降落时,自动驾驶仅仅因为垃圾回收而冻结4秒钟。 大多数嵌入式计算机的CPU和内存都比您的智能手机少:对内存和调度的控制不是选择,而是必须的。 - LoneWanderer

8

首先,语言本身并没有真正“禁止”垃圾回收。

其次,一些实现确实执行垃圾回收。特别是,所有针对JVM的实现都进行垃圾回收。

第三,所有编译器都有一种方式来获取一定数量的垃圾回收。你看,当访问类型超出范围时,如果你明确告诉语言为其对象分配一定的存储空间,则该空间将在此时被销毁。我过去曾使用过这个方法来获得一定程度的垃圾回收。你需要使用声明式语法:

type Foo is access Blah;
for Foo'storage_size use 100_000_000; --// 100K

如果这样做,那么由Foo指针指向的所有(100K的)Blah对象分配的内存都将在Foo类型超出范围时清除。由于Ada允许您在其他子程序中嵌套子程序,因此这特别强大。
要了解storage_size和storage pools能为您做什么,请参见LRM 13.11
第四,编写良好的Ada程序不像C程序那样过度依赖动态内存分配。C有许多设计漏洞,从而使从业者学会使用指针来弥补。在Ada中,很多这些惯用法是不必要的。

8
答案更加复杂:由于实时约束等原因,Ada不需要垃圾回收器。然而,该语言已经被巧妙地设计,以允许垃圾回收器的实现。
虽然许多(几乎所有)编译器都不包括垃圾回收器,但有一些值得注意的实现:
- GNAT的补丁 - 针对Java虚拟机的Ada编译器(我不知道这些项目是否仍受支持)。它使用了JVM的垃圾回收器。
网络上有很多关于Ada中垃圾回收的其他来源。这个主题已经被广泛讨论,主要是因为在90年代中期与Java的激烈竞争中(看看这个页面"Ada 95是Java语言应该成为的样子"),Java成为“下一个大事件”之前,微软推出了C#。

0

首先,我想知道现在谁在使用Ada。我实际上喜欢这种语言,甚至还有一个针对Linux/Ada的GUI库,但是多年来我没有听到任何关于活跃的Ada开发的消息。由于其与军方的联系,我真的不确定它是否是古代历史,或者是如此成功以至于所有关于它的使用都是机密的。

我认为Ada没有垃圾回收的原因有几个。首先,它的诞生年代是大多数编译语言主要使用堆栈或静态内存,或者在一些情况下显式堆分配/释放的时代。 GC作为一种通用哲学真正流行起来是在1990年左右,当OOP、改进的内存管理算法和足够强大以节省运行所有内容所需的循环的处理器开始发挥作用时。仅仅将Ada编译就足以使1989年的IBM 4331大型机无法承受。现在,我的手机可以超越那台机器的CPU。

另一个好的原因是,有人认为严格的程序设计包括对内存资源的精确控制,并且不应容忍让动态获取的对象漂浮。可悲的是,随着动态内存越来越成为规则,太多的人最终会泄漏内存。此外,就像汇编语言相对于高级语言的“效率”,以及原始JDBC相对于ORM系统的“效率”一样,手动内存管理的“效率”在扩展时往往会逆转(我见过ORM基准测试,其中JDBC相当于只有一半的效率)。虽然这可能违反直觉,但现在的系统在全局优化大型应用方面要好得多,而且能够针对表面上微小的变化做出彻底的重新优化,包括根据检测到的负载实时重新平衡算法。
关于那些说实时系统无法承受GC内存的人,我恐怕必须持不同意见。现在我们有了更加智能的方法来回收内存,GC已经不再是每隔几分钟就会冻结整个系统的东西了。

嗯,Ada标准的新版本正在开发中,目前正在积极推进。GCC Ada编译器也在积极开发中。 - T.E.D.
2
我认为你在第三段中说的有点道理。Ada绝对是一种迎合控制狂人的语言。如果你愿意,你可以进去告诉编译器每个记录类型中的每个字段使用哪些偏移量和哪些位。在C语言中甚至都不能这样做。喜欢这种事情的人不太可能是松散动态内存使用实践的粉丝。 - T.E.D.
1
我来加一个有趣的第三条评论。你最后一段让我很好奇,所以我去看了一下。在我看来,仍然没有可用的确定性通用GC算法。有一些算法试图通过限制每次执行的工作量来模拟它,但这只会给你一个可能会落后的GC(最终耗尽内存)。 - T.E.D.

0

你的问题是不正确的。它确实有用。请参见 ada.finalization 包,它可以为您处理垃圾回收。


这实际上更像是一种提供RAII的方法,就像您可以使用典型的C++类一样。如果您想将其用于在Ada类中分配的GC内容,则必须编写它(正确!)以执行此操作。它与Java要求其编译器进行的通用无脑GC不完全相同。但是,我相信所有针对JVM(Java虚拟机)的Ada编译器都使用其垃圾回收功能。 - T.E.D.
现在很少需要自己编写Finalize。您可以使用Ada.Containers来处理需要动态调整大小的对象。 - Zerte

0

我想分享一个非常简单的示例,演示如何实现Free()过程(这将以所有C程序员熟悉的方式使用)...

with Ada.Integer_Text_IO, Ada.Unchecked_Deallocation;
use Ada.Integer_Text_IO;

procedure Leak is
   type Int_Ptr is access Integer;
   procedure Free is new Ada.Unchecked_Deallocation (Integer, Int_Ptr);

   Ptr : Int_Ptr := null;
begin
   Ptr := new Integer'(123);
   Free (Ptr);
end Leak;

在程序的末尾调用Free将会将分配的整数返回给存储池(在C中称为“堆”)。你可以使用valgrind来证明这确实防止了4个字节的内存泄漏。
Ada.Unchecked_Deallocation(一个通用定义的过程)可以用于(我认为)任何可能使用“new”关键字分配的类型。Ada参考手册(“13.11.2 Unchecked Storage Deallocation”)有更多详细信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接