15

In Delphi XE, the following code will cause memory leak:

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

  P := procedure
       begin
         B;
       end;
end;

Run the code with

ReportMemoryLeaksOnShutdown := True;

and the memory manager prompt:

21-28 bytes: TForm1.Button1Click$ActRec x 1
Chau Chee Yang
  • 18,422
  • 16
  • 68
  • 132
  • 2
    @Ken: 21-28 bytes in this particular (highly simplified) example. But if this is happening in a loop, that can add up over time... – Mason Wheeler Jun 08 '11 at 02:32
  • @Mason: I'm not sure why you would use anonymous methods in any type of loop where this would matter. Maybe I'm missing something, but this probably would only be an issue in an app that runs a long time (like on a server), and I'd question the use of the methods in that case. – Ken White Jun 08 '11 at 02:45
  • @Ken: Just off the top of my head, check out OmniThreadLibrary, which can be highly useful for heavily multithreaded apps (such as servers!) and uses a lot of anonymous methods and loops under the hood. – Mason Wheeler Jun 08 '11 at 02:52
  • 3
    @Ken: The anonymous method shown in the example is not meaningful. I post this question to try to illustrate a simple usage of anonymous method that cause memory leaks. I already simplified my example here to pin point the problem. My real case is far longer and meaningless for asking in SO. – Chau Chee Yang Jun 08 '11 at 03:15

3 Answers3

14

This is due to the way anonymous methods work. Anonymous methods are implemented as TInterfacedObject descendants, and if you have more than one in the same routine, they end up as two methods of the same object. It uses interfaces for reference counting so you don't end up leaking the objects. However, if an anonymous method references itself, that ends up throwing off the reference count and causing a memory leak. What you're seeing here is caused by a combination of these two things.

Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477
  • Shall we consider this is a bug? Or this is it's nature working behavior? – Chau Chee Yang Jun 08 '11 at 03:18
  • 1
    @Chau: See Barry Kelly's comment on the second post. This is its normal behavior, and it's a problem without an easy solution. If you want this to not happen, you'll have to restructure your code in some way. – Mason Wheeler Jun 08 '11 at 03:38
  • I just recognized that the [official docs are very verbose about this particular topic](http://docwiki.embarcadero.com/RADStudio/Berlin/en/Anonymous_Methods_in_Delphi). It comes with a number of examples and a whole bunch of warnings re potential memory leaks. Probably that's not a coincidence ... especially if you don't realize that two anon methods may affect each other behind the scenes, only because they end up in the same instance. – JensG Jun 22 '16 at 12:22
13

This is a bug in the compiler (as far as I know). I opened QC83259 in Embarcadero's quality central about it.

You can work around this bug by creating the anonymous procedure in a routine. The following code won't leak.

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;
Ken Bourassa
  • 6,363
  • 1
  • 19
  • 28
  • Unfortunately now our anonymous method in `TForm1.GetMethod` won't have access to local variables in `Button1Click` captured in the closure. In many cases this defeats the main purpose of using an anonymous method rather than a regular method. – Ian Goldby Dec 11 '13 at 11:31
4

I know I'm 2 years late to this discussion, but I've recently run into this memory leak within our code and I couldn't get Ken's suggested answer to work. So with the help of a colleague of mine we came up with a different answer to keep using the nested anonymous methods yet avoid any memory leaks.

Below is an example of the solution we found:

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

        P := procedure
        begin
          B;
        end;

        B := nil;
    end;

My belief is that due to the way local variables are bound for the purpose of extending their lives in order for the anonymous method to use it outside of the scope it was created in, that it is making a copy of the underlying interfaced object in order to move the variable from the stack to the heap, and in doing so it is calling AddRef which increments the reference counter. Setting the variable to nil after it has been used calls the Release which in turn decrements the reference counter back down to 0 which allows the interfaced object to be freed.

After doing this we have not seen the memory leaks that were occurring from before.

Whether this is a bug or not, I cannot answer that, but I am interested in hearing the opinions from others. We see this as a way to allow us to continue using anonymous methods in a nested form like this.

Vysion
  • 41
  • 2
  • To my surprise (maybe I shouldn't be surprised) this also solves the problem for an anonymous method called recursively. Thank you! – Ian Goldby Dec 11 '13 at 11:53