Jump to content

Recommended Posts

Yes, they work 🙂 (and why wouldn't they?). 

 

program RecursiveAnon;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

function MakeFib: TFunc<integer,int64>;
var
  fib: TFunc<integer,int64>;
begin
  fib :=
    function (value: integer): int64
    begin
      if value < 3 then
        Result := 1
      else
        Result := fib(value - 1) + fib(value -2);
    end;
  Result := fib;
end;

begin
  var fib := MakeFib;
  for var:= 1 to 10 do
    Write(fib(i), ' ');

  Readln;
end.

 

  • Like 2
  • Thanks 1

Share this post


Link to post
10 minutes ago, Sherlock said:

But is it still anonymous though?

 

Of course it is! You can do this:

for:= 1 to 10 do
  Writeln(MakeFib()(i), ' ');

 

As Stefan noted on a side-channel, calling MakeFib causes a memory leak because the anonfunc interface now contains a cyclic reference to itself.

Edited by Primož Gabrijelčič
  • Like 1

Share this post


Link to post
21 minutes ago, Primož Gabrijelčič said:

As Stefan noted on a side-channel, calling MakeFib causes a memory leak because the anonfunc interface now contains a cyclic reference to itself.

var
  [weak] fib: TFunc<integer,int64>;

Marking it as weak breaks the cycle. However, there was a bug with weak that is only recently fixed (not sure whether in 10.3 or 10.3.1)

  • Like 5
  • Thanks 1

Share this post


Link to post

Can anyone explain about memory leak? Is it not deleted when exiting function? Need more examples.

Share this post


Link to post
Quote

Note: The use of weak and unsafe references was originally introduced as part of the ARC memory management support for mobile platforms. As ARC is now phased out, this feature remains available only for interface references.

 

Share this post


Link to post

To understand this, you need to understand how anonymous methods are implemented.

 

The compiler implements them by creating a class that inherits from TInterfacedObject. The anonymous method type is a special interface with one method called Invoke, which has the signature of the anonymous method type.

When you add ReportMemoryLeaksOnShutdown := True to the code above you will see that it reports one instance of a class called MakeFib$ActRec - that is the class name of the compiler-generated class for the anonymous method.

The class also contains fields for every captured variable - in this case the local variable fib is being captured with holds the reference to itself so this is the circular interface reference that causes the memory leak.

 

To demonstrate here is the code as the compiler implements it:

 

type
  MakeFibAnonClass = class(TInterfacedObject, TFunc<integer,int64>)
    fib: TFunc<integer,int64>;
    function Invoke(value: integer): int64;
  end;

function MakeFibAnonClass.Invoke(value: integer): int64;
begin
  if value < 3 then
    Result := 1
  else
    Result := fib(value - 1) + fib(value -2);
end;

function MakeFib_Impl: TFunc<integer,int64>;
var
  obj: MakeFibAnonClass;
begin
  obj := MakeFibAnonClass.Create;
  obj.fib := obj;
  Result := obj.fib;
end;

 

  • Like 2

Share this post


Link to post
3 hours ago, Stefan Glienke said:

To demonstrate here is the code as the compiler implements it:

Inside MakeFib_Impl(), the object reference should be an interface reference instead, to better match what the compiler actually produces:

function MakeFib_Impl: TFunc<integer,int64>;
var
  fib: TFunc<integer,int64>;
begin
  fib := MakeFibAnonClass.Create; // refcnt is 1
  obj.fib := fib; // refcnt is now 2
  Result := fib;
end;

Although this is a gross oversimplification, as the actual compiler implementation is much more complex (see Variable Binding Mechanism for details).  The local fib variable inside of the calling function, and the captured fib variable inside the anonymous method, are actually the same variable in memory.  But yes, the self-referencing issue still applies (just on a separate frame object that holds the fib variable).

 

This self-referencing issue is even mentioned in the documentation:

Quote

It is possible to create a cycle in the method reference/frame link chains that causes a memory leak. For example, storing an anonymous method directly or indirectly in a variable that the anonymous method itself captures creates a cycle, causing a memory leak.

 

  • Like 1

Share this post


Link to post
3 hours ago, Remy Lebeau said:

The local fib variable inside of the calling function, and the captured fib variable inside the anonymous method, are actually the same variable in memory. 

There is no local variable - on the source code level, yes there is, but implementation wise they live inside the heap memory of the compiler generated instance.

That's why captured variables never appeared in the local variables view until recently when the kinda implemented support for it.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×