3

假设我有一个具有深度绑定的静态/词法范围语言,并且我创建了一个闭包。闭包将包括我想要执行的语句以及所谓的引用环境,或者引用这篇文章,可以使用的变量集合。

这个引用环境在实现方面实际上看起来像什么?我最近在阅读有关 ObjectiveC 块的实现的内容,作者建议在幕后您可以获得堆栈上所有变量的副本以及对堆对象的所有引用。该解释声称您在创建闭包时获得了引用环境的“快照”。

  1. 或多或少会发生这种情况,还是我误读了?
  2. 是否采取任何措施来“冻结”堆对象的单独副本,或者假设如果它们在闭包创建和闭包执行之间被修改,闭包将不再在对象的原始版本上运行?
  3. 如果确实进行了复制,那么在可能想要创建大量闭包并将它们存储在某处的情况下是否有内存使用注意事项?

我认为对其中一些概念的误解可能会导致棘手的问题,例如 Eric Lippert 在这篇博文中提到的问题。这很有趣,因为您会认为保留对在调用闭包时可能已经消失的值类型的引用是没有意义的,但我猜在 C# 中编译器会找出变量稍后需要并将其放入堆中。

似乎在大多数内存管理的语言中,一切都是引用,因此 ObjectiveC 是一个有点独特的情况,必须处理复制堆栈上的内容。

4

2 回答 2

3

这是一个类似伪javascript语法的运行示例。

function f(x) {
  var y = ...;
  function g(z) {
    function h(w) {
      .... y, z, w ....
    }
    .... x, h ....
  }
  .... x, g ....
}

一种表示是环境的链接链。也就是说,闭包由代码指针、一些槽和对封闭闭包或顶级环境的引用组成。在这个表示中,

f = [<code>, <top-level-env>]
g = [<code>, f, x, y]
h = [<code>, g, z]

除了有时最好让每个函数都直接引用顶级环境,因为它经常使用:

f = [<code>, <top-level-env>]
g = [<code>, <top-level-env>, f, x, y]
h = [<code>, <top-level-env>, g, z]

(也有其他变化。)

这种表示的一个优点是您可以将可变变量直接存储在闭包中。(好吧,也许,取决于你如何表示函数激活。)如果你有深度嵌套的闭包,一个缺点是一些变量可能需要多次跳转才能到达。另一个缺点是,如果一个闭包比它的父闭包寿命更长(例如,greturns h),那么这种表示可能会阻止 GC 收集大部分或什至完全不可达的环境帧。

另一种表示是“平面闭包”:每个闭包都包含一个代码指针和所有代码自由变量的插槽。

g = [<code>, x, y]
h = [<code>, y, z]

这种表示修复了空间/GC问题;noclosure 将另一个闭包固定在内存中。另一方面,自由变量槽被复制而不是共享,所以如果有一个带有许多自由变量的嵌套闭包——或者嵌套闭包的许多实例——整体内存使用可能会更高。此外,这种表示通常需要存储可变变量以进行堆分配(但仅适用于实际发生突变的变量,并且仅在无法自动重写突变时)。

还有混合方法。例如,您可能有大部分是扁平的闭包,但会特别对待顶级环境:

g = [<code>, <top-level-env>, x, y]

或者你可能有一个“足够聪明”(或至少“足够雄心勃勃”)的编译器,它试图根据自由变量的数量、嵌套深度等在表示之间进行选择。

于 2012-09-04T21:28:05.030 回答
2

在 Smalltalk 中,闭包可以保存对“外部上下文”的引用。外部上下文通常是创建闭包的方法的堆栈帧,但对于嵌套闭包,它可能是另一个闭包。

持有对外部上下文的引用的闭包是昂贵的,因为(我猜)它们会阻止相应的堆栈被垃圾收集。因此,闭包仅在真正需要时才引用外部上下文:

干净的闭包:不引用任何本地内容的闭包。他们不需要引用外部上下文。

例如[ Transcript show: 'something' ]

复制闭包:引用变量的闭包在创建闭包后不会更改。创建闭包时的变量值被复制到闭包本身中。然后,不需要保留对外部上下文的引用。例如

| 列表 |
列表:= OrderedCollection 新。
1 到:5 做:[ :i | 列表添加:我]。

全闭包:保持对外部上下文的引用的闭包。例如

| 柜台 |
计数器 := 0。1
到:5 做:[ :i | 计数器:= 计数器 + 1]。

如果在创建闭包后关闭的变量发生了变异,则需要完全闭包,但对于非本地返回也是如此。关于非本地退货,您可能会喜欢Neal Gafter 的这篇博文

值得一读的还有 Brian Goetz 的关于即将到来的 JDK 7 中的闭包的 Lambda 状态。除此之外,我发现关于为什么他们将坚持使用 Java 限制以仅捕获最终的最终变量并禁止捕获可变局部变量的讨论很有趣。将不支持上述完全关闭的示例。他们声称的论点是它主要是连环成语。

于 2012-09-03T06:54:58.963 回答