具有手动内存管理的解释型语言?

11

有哪些指针无关的语言(比如Python、Java、Perl、PHP、Ruby、JavaScript等),支持手动内存管理?我不记得听说过有这样的语言。

难道关于解释型语言的主要问题不是垃圾回收的非确定性延迟(或者当没有足够延迟时的空间复杂度)吗?那为什么不编写一个与Java非常相似,但强制你手动释放内存的语言呢?

编辑

我所说的手动内存管理是指该语言会有对象的引用,并且你可以使用引用删除对象。

例如:

Object a = new Object(); // a is a reference to the object
Object b = a; // b is a reference to the same object
a.method(); // fine
delete b; // delete the object referenced by b
a.method(); // null dereference exception

除了内存泄漏之外,像这个例子中的语言可能存在哪些注意事项?


1
顺便问一下,在这里你所说的“interpreted”是什么意思?在现今的字节码时代,Java和Python、PHP或Javascript一样都是“interpreted”的。也许你更准确地提到“动态类型”语言会更好些? - jsbueno
任何由解释器执行的东西,无论是某种中间形式还是纯字节码。特别是像php/java/perl/python/ruby这样的东西,它们不会让你破坏地址空间。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
我认为合适的术语可能是使用托管指针而不是裸指针的运行时。当然,它们并不是互斥的。 - Steve g
这是一个微软的术语吗?我从来没有听说过。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
我实际上一直在努力寻找一种语言(或处理器)的定义,它不允许您破坏堆栈或地址空间,基本上任何没有指针的东西都倾向于匹配这个条件。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
10个回答

23

这个问题的前提有点靠不住:

  • 内存模型语言的属性,而不是它的实现。

  • 被解释是实现的属性,而不是语言。

例如:

  • 编程语言 Scheme 具有自动内存管理功能,并且有许多十几种解释器的实现,但也有一些很好的本地代码编译器,包括 Larceny、Gambit 和 PLT Scheme(其中包括一个解释器和 JIT 编译器使无缝过渡)。

  • 编程语言 Haskell 具有自动内存管理功能;最著名的两个实现是解释器 HUGS 和编译器 GHC。还有其他几个光荣的实现,平均分为编译为本地代码(yhc)和解释(Helium)。

  • 编程语言 C 具有手动内存管理功能,虽然世界上到处都是 C 编译器,但我们足够老以记得辉煌的 1980 年代可能会记得 Saber-C 或 C-terp,这是两个非常有用的 MS-DOS C 解释器。

尽管如此,你的问题背后有一个真实的观察结果:具有手动内存管理的语言通常是编译的。为什么?

  • 手动内存管理是一个传统特性,通常用于与传统代码兼容。传统语言通常已经成熟到拥有本地代码编译器。

  • 许多新语言是由实现定义的。构建解释器比构建编译器更容易。在解释设置中,实现简单的自动内存管理比在本地代码编译器中实现高性能自动内存管理更容易。因此,如果语言的定义来自其第一个实现,则自动内存管理与解释相关,因为在解释设置中,实现更容易。

手动内存管理有时候被用来提高性能。Ben Zorn在上世纪90年代进行的实验研究表明,自动内存管理与手动内存管理的速度相同或更快,但需要大约两倍的内存。所以手动内存管理经常用于内存稀缺的非常小的设备和非常大的数据中心,在这些场景下,增加一倍的内存成本昂贵。(有些人不了解内存管理,只听说过垃圾回收很慢,所以也会使用手动内存管理。他们在1980年是正确的。)而当涉及到性能问题时,通常会选择本地代码编译器而不是解释器。
真正有趣的一些例外情况也来自于这个原则。例如,FORTH和最早的PostScript实现都是为运行在内存资源匮乏但计算时间不是问题的小型嵌入式设备(望远镜和打印机)而设计的。这两种语言都是首先使用比本地代码更紧凑的字节码实现,并且都具有手动内存管理功能。因此:带有手动内存管理的解释器。 (后来的PostScript版本添加了一种垃圾回收选项。)
总之:
自动内存管理与手动内存管理是“语言”; 编译与解释是“实现”; 在原则上,这两个选择可以且通常是正交的,但出于实际工程原因,自动内存管理经常与解释相关联。
“那么,解释语言的主要问题不是垃圾回收的非确定性延迟(或者当延迟不足时的空间复杂度)吗?”我之前并不知道针对解释型编程语言有很大的担忧。按字母顺序排列,Lua、Perl、PostScript、Python和Ruby都非常成功,Icon、Scheme和Squeak Smalltalk都是中等成功的语言。唯一引起担忧的是在硬实时计算领域中出现的不可预测的延迟,比如控制汽车刹车的ABS系统(如果您驾驶高级轿车)。
注:问题编辑后添加了注意事项:将“解释型”更改为“无指针”。但是您在评论中说您想问的是具有“new”和“delete”的语言。任何具有“new”和“delete”的语言都有指针:根据定义,无论“new”返回什么都是指针。(在某些语言中,可能还有其他来源的指针。)所以我认为您要问的是“没有指针算术和没有取地址运算符的语言”。

手动内存管理,如 newdelete 操作符。试图取消引用一个已经被 delete 的对象将会导致异常或其他错误,而不是未定义的行为。基本上与 Java 相同,但具有删除操作符,就像我的例子一样。你提到手动内存管理是遗留问题是有道理的。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

5

Forth有堆栈式的内存区域,可以使用FORGET释放。


4
有哪些解释型语言需要手动管理内存?我从未听说过。
实际上,不存在所谓的“解释型语言”。语言既不是编译的也不是解释的。语言只是存在的一堆抽象数学规则。解释或编译是语言实现的特性,与语言本身无关。每一种语言都可以通过编译器或解释器来实现;大多数现代高性能语言实现实际上同时使用两者,并根据特定情况切换它们中的快速者。
C和C ++是具有手动内存管理的解释型语言的例子,因为每种语言都可以拥有解释型实现。另一个例子是1958年的第一版Lisp:它具有手动内存管理(基于引用计数),但仅在几个月后被自动内存管理的版本替换,自那以后就一直使用自动内存管理。如果您放松一下标准并意识到内存管理只是一般资源管理的特例,那么您会发现几乎所有语言都有某种形式的手动资源管理。

我认为他所说的“解释性”只是指像Python、Perl等脚本语言,你是对的,但这不是讨论这个问题的正确场所 =P。 - Claudiu
确实,你说得没错,但这与我的问题无关。我会试着具体说明我所谓的解释性语言: "内存安全(不一定是“脚本式”)的语言,不像C/C++那样接近底层,除非你真的想要进行垃圾回收,否则不会让你破坏地址空间; 这些语言通常是 JIT 编译和/或解释执行的(甚至在此过程之前编译为字节码(或者预先编译)。" - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

2

在一些高性能的解释型编程语言中,比如Lua,你可以手动处理垃圾回收。请参阅lua_gc


2
有一些C/C++的解释器可用,例如这个
我自己没有尝试过,但我认为它声称兼容已编译的C/C++,因此需要具有“手动”内存管理。

1
原因是循环引用、空指针异常和多重引用。一个简单的例子:
var a = new Object();
var b = a;
a = null;//or delete a or free a or whatever;
print(b);//what now? is b null? or is b still new Object()?

如果在上面的例子中,b现在为null,当重新定义变量时会遇到一些严重的问题。例如,如果将a设置为c,而不是将其设置为null,那么b也会是c吗?
您可以阅读维基百科上关于循环引用等其他问题的内容。

循环引用问题可以通过基于区域的内存管理来解决。早期的手动方法包括竞技场和基于堆栈的内存管理,例如Forth的FORGET。 - Doug Currie
是的,空引用本来没问题,但我没有考虑到引用别名的情况,这会使事情变得更加复杂。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

1

因此,回答问题的这一部分:

主要关注的不是解释语言中的 非确定性延迟(或者在没有足够的延迟时的空间复杂度)垃圾收集吗?那么为什么不像Java一样编写一些代码,但强制手动释放内存呢?

这可能是某些系统的问题。对于其他系统来说并不是那么大的问题。使用垃圾收集的软件可以比只调用malloc的系统更快地分配内存。当然,您最终会在GC时间付出时间成本。

以基于Web的系统为例。您可以在处理请求期间分配所有内存,GC可以在之后进行收集。它可能不会完全按照这样的方式工作,但您可以理解这个想法。

有许多不同的垃圾收集策略。哪种策略最适合系统将取决于要求。但即使您需要绝对确定性,也可以使用类似于:实时Java 的东西。


0

解释型并不一定意味着垃圾回收。Perl、Tcl、Python等等,我相信都使用简单的引用计数,因此内存回收是确定性的,但并不透明(你曾经尝试过在Perl程序上运行strace吗?)。


引用计数是一种垃圾回收类型,尽管存在缺陷,但由于其易于实现且不依赖于平台而经常使用。 - David Thornley
是的,但这只是术语。我指的是内联引用计数与在Java/C#中后台进行垃圾回收。 - Nikolai Fetissov
是的,我知道Python使用引用计数。我说的是显式删除对象,这样通过一个引用进行删除并尝试通过任一引用访问该对象时将产生异常。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
难道发现和消除循环引用的时间不是不确定的吗? - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

0
Python的API官方允许开启或关闭延迟垃圾回收 - 请查看标准库中“gc”模块的文档:

http://docs.python.org/library/gc.html

但与静态语言相比,这并不是使其变慢的原因 - 数据本身的动态性是速度差异的主要原因。


0
编程语言Haskell具有自动内存管理功能;最著名的两个实现是解释器HUGS和编译器GHC。还有其他几个值得称赞的实现,大致均分为编译成本地代码(yhc)和解释(Helium)。此外,Haskell还具备显式内存管理的能力。
import qualified Haskus.Memory.Allocator.Malloc as Malloc

Malloc.newBuffer          :: MonadIO m => Word -> m (Maybe BufferME)
Malloc.newFinalizedBuffer :: MonadIO m => Word -> m (Maybe BufferMEF)
Malloc.freeBuffer         :: MonadIO m => BufferME -> m ()

如我们在λ Haskell中清楚地看到的那样,你也可以进行手动内存管理!XD
更多信息请参见:hakell

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