内存泄漏发生在嵌套的匿名方法中。

16
在 Delphi XE 中,以下代码会导致内存泄漏:
procedure TForm1.Button1Click(Sender: TObject);
var P, B: TProc;
begin
  B := procedure
       begin
       end;

  P := procedure
       begin
         B;
       end;
end;

使用以下命令运行代码

ReportMemoryLeaksOnShutdown := True;

并且内存管理器提示:

21-28 bytes: TForm1.Button1Click$ActRec x 1

2
@Ken:在这个特定的(高度简化的)示例中,是21-28个字节。但如果这在一个循环中发生,随着时间的推移,这些字节会累积起来... - Mason Wheeler
@Mason:我不确定为什么你会在任何需要注意这个问题的循环中使用匿名方法。也许我漏掉了什么,但这可能只会在长时间运行的应用程序(比如服务器上)中成为一个问题,而在那种情况下,我会质疑使用这些方法的必要性。 - Ken White
@Ken:就我个人而言,建议你看一下OmniThreadLibrary,它对于高度多线程的应用程序(例如服务器!)非常有用,并且在底层使用了许多匿名方法和循环。 - Mason Wheeler
3
@Ken:示例中展示的匿名方法并没有实际意义。我发布这个问题是为了说明使用匿名方法可能会导致内存泄漏的简单用法。我已经在这里简化了示例以明确问题。��的真实案例要长得多,而且没有必要在SO上询问。 - Chau Chee Yang
3个回答

14

我们应该把这看作是一个bug吗?还是这是它本来的工作行为? - Chau Chee Yang
1
@Chau:请看第二篇帖子中Barry Kelly的评论。这是它的正常行为,而且它是一个没有简单解决方案的问题。如果你不想发生这种情况,你必须以某种方式重构你的代码。 - Mason Wheeler
我刚刚意识到官方文档在这个特定主题上非常冗长。它提供了许多示例和大量关于潜在内存泄漏的警告。这可能不是巧合...特别是如果您没有意识到两个匿名方法可能会在幕后相互影响,只因为它们最终出现在同一个实例中。 - JensG

13
这是编译器的一个错误(据我所知)。 我在Embarcadero的质量中心打开了QC83259的相关问题。
您可以通过在例程中创建匿名过程来解决此错误。 以下代码不会泄漏。
procedure TForm1.Button1Click(Sender: TObject);
var P, B: TProc;
begin
  B := GetMethod(); //Note, the "()" are necessary in this situation.
  P := procedure
  begin
    B;
  end;
end;


function TForm1.GetMethod: TProc;
begin
  Result := procedure
  begin
  end;
end;

不幸的是,现在我们在 TForm1.GetMethod 中的匿名方法将无法访问在闭包中捕获的 Button1Click 中的局部变量。在许多情况下,这会破坏使用匿名方法而不是常规方法的主要目的。 - Ian Goldby

4

我知道我晚了2年才参与这个讨论,但最近我们的代码中出现了内存泄漏问题,我无法使用Ken提供的答案解决。因此,在我的一位同事的帮助下,我们想出了一个不同的答案来继续使用嵌套匿名方法,同时避免任何内存泄漏。

以下是我们找到的解决方案示例:

    procedure TForm1.Button1Click(Sender: TObject);
    var P, B: TProc;
    begin
        B := procedure
        begin
        end;

        P := procedure
        begin
          B;
        end;

        B := nil;
    end;

我的看法是,由于本地变量的绑定方式是为了延长其生命周期,以便匿名方法可以在创建它的范围之外使用它,因此它正在复制底层接口对象,以将变量从堆栈移动到堆中,并在此过程中调用AddRef,增加引用计数器。在使用变量后将其设置为nil会调用Release,这会将引用计数器递减回0,从而允许释放接口对象。

这样做后,我们没有看到之前发生的内存泄漏。

无论这是否是一个错误,我不能回答,但我很想听听其他人的意见。我们认为这是一种允许我们继续像这样嵌套使用匿名方法的方法。


令我惊讶的是(也许我不应该感到惊讶),这也解决了递归调用的匿名方法的问题。谢谢! - Ian Goldby

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