Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory leak when utilizing the reference counting feature of Delphi

Tags:

delphi

The following code try to utilize the reference counting feature of Delphi.

However, FastMM4 in FullDebugMode reports DoStuff1 gives memory leak while DoStuff2 does not. Could you help to comment about why ? Shouldn't these two procedures behave exactly the same behind the scene ?

program Project_SO;

{$APPTYPE CONSOLE}

uses
  FastMM4, 
  SysUtils;

type  
  ITestFunc = interface
  ['{B3F6D9A7-FC77-40CE-9BBF-C42D7037A596}']
    function DoIt(X,Y: Integer): Integer;
  end;

  TTestFunc = class(TInterfacedObject, ITestFunc)
  public
    function DoIt(X,Y: Integer): Integer;
  end;
  TTestFuncClass = class of TTestFunc;   

{ TTestFunc }

function TTestFunc.DoIt(X, Y: Integer): Integer;
begin
  Result := X + Y; 
end;

function DoStuff1(Num1, Num2: Integer; OperationClass: TTestFuncClass): Integer;
begin
  Result := ITestFunc(OperationClass.Create).DoIt(Num1, Num2);
end;

function DoStuff2(Num1, Num2: Integer; OperationClass: TTestFuncClass): Integer;
var I: ITestFunc;
begin
  I := ITestFunc(OperationClass.Create);
  Result := I.DoIt(Num1, Num2);
end;

begin
  Writeln(IntToStr(DoStuff1(3, 6, TTestFunc)));
  Writeln(IntToStr(DoStuff2(3, 6, TTestFunc)));
end.
like image 674
SOUser Avatar asked Nov 23 '15 02:11

SOUser


People also ask

How do I report memory leaks in Delphi?

1 System.ReportMemoryLeaksOnShutdown. In latest Delphi versions 2006 or later we can set ReportMemoryLeaksOnShutdown = True before Application.Run code in Project file to report memory leaks on shutdown. 2 FASTMM. FastMM is actually the memory manager included with Delphi since a few years. ... 3 Memcheck. ... 4 MadExcept. ... 5 Eurekalog. ...

Why doesn't Delphi support automatic reference counting of variables?

The reason for that is that Delphi's automatic reference counting only works with interfaces. With variables declared as interfaces, automatic reference counting will be handled by the compiler, and you will not have to put in any deallocation calls in order to prevent memory leaks.

What's new in the Delphi memory manager?

All Delphi versions since Delphi 2006 have an updated memory manager that is faster and more feature rich. One of the nicest features of the "new" memory manager allows applications to register (and unregister) expected memory leaks, and optionally report unexpected memory leaks on program shutdown.

What is Memcheck in Delphi?

MemCheck hunts memory leaks, memory corruption, use of an object after its destroying, method calls on interface references which refer to a destroyed object, etc. MemCheck is freeware, with source. 4. MadExcept madExcept was built to help you locating crashes in your Delphi application.


1 Answers

Result := ITestFunc(OperationClass.Create).DoIt(Num1, Num2);

Nowhere here is a reference to the interface taken. A reference is taken when the interface is assigned to a variable, or passed as a by value parameter. In fact, passing as a by value parameter, can be thought of as semantically equivalent to assigning to a local variable in the callee's frame.

But nowhere in this code is a reference taken. And so, since nothing has a reference to the interface, there is no mechanism for it to be destroyed. Hence the leak.

var 
  I: ITestFunc;
begin
  I := ITestFunc(OperationClass.Create);
  Result := I.DoIt(Num1, Num2);
end;

In this variant, a reference is taken when the assignment is made. When the local variable I leaves scope, its reference count decreases to zero and the implementing object is destroyed.

Note that the unchecked cast is needless here. The compiler knows perfectly well that TTestFunc implements ITestFunc and your code should better be written like this:

var 
  I: ITestFunc;
begin
  I := OperationClass.Create;
  Result := I.DoIt(Num1, Num2);
end;

As suggested in the comments, you could remove the local variable and use a checked as cast:

Result := (OperationClass.Create as ITestFunc).DoIt(Num1, Num2);

A consequence of the implementation of the as cast is that an implicit local variable is declared, to which the interface is assigned. That means that the reference count is incremented to one, and then decremented to zero when that implicit local leaves scope.

Finally, your TTestFunc class should have a virtual constructor since you intend to instantiate it with a meta class.

like image 151
David Heffernan Avatar answered Sep 29 '22 20:09

David Heffernan