为什么会出现内存泄漏以及如何解决?

时间:2011-08-07 17:03:24

标签: delphi pointers memory-leaks

unit Unit7;

interface

uses Classes;

type
  TListener = class(TThread)
    procedure Execute; override;
  end;

  TMyClass = class
    o1,o2: Tobject;
    procedure FreeMyObject(var obj: TObject);
    constructor Create;
    destructor Destroy; override;
  end;

implementation

uses Windows, SysUtils;

var l: TListener;
    my: TMyClass;

procedure TListener.Execute;
var msg:TMsg;
begin
  while(GetMessage(msg, Cardinal(-1), 0, 0)) do
    if(msg.message=6) then begin
      TMyClass(msg.wParam).FreeMyObject(TObject(msg.lParam));
      Exit;
    end;
end;

constructor TMyClass.Create;
begin
  inherited;
  o1:=TObject.Create;
  o2:=Tobject.Create; // Invalid pointer operation => mem leak
end;

destructor TMyClass.Destroy;
begin
  if(Assigned(o1)) then o1.Free;
  if(Assigned(o2)) then o2.Free;
  inherited;
end;

procedure TMyClass.FreeMyObject(var obj: TObject);
begin
  FreeAndNil(obj);
end;

initialization
  l:= TListener.Create();
  my:=TMyClass.Create;

  sleep(1000); //make sure the message loop is set
  PostThreadMessage(l.ThreadID, 6, Integer(my), Integer(my.o2));
finalization
  l.Free;
  my.Free;
end.

我使用消息处理程序来说明我的问题,以便您理解它。真正的设计要复杂得多。函数'FreeMyObject'实际上是Frees AND使用多态范例创建一个实例,但这不是必需的。我只想指出设计应该保持不变。

现在问题和问题 - 为什么会发生以及如何解决?似乎'如果Assigned(o2)'不适合它。

我的想法:发送一个指向my.o2的指针将自由并且nil o2并且我尝试这样做,但我无法在消息处理程序中从指针转换为对象,不知道为什么。

有人可以伸手吗?感谢

3 个答案:

答案 0 :(得分:6)

您可以免费o2两次。一旦作为消息的结果,一次来自析构函数。

当您致电o2时,您认为自己将nil设为FreeMyObject,但事实并非如此。您实际上将msg.lParam设置为0。

o2是一个包含对象引用的变量。您传递的值为o2,当您按值传递时,您无法修改传递其值的变量。因此,您需要传递对o2的引用。为此,您需要添加额外级别的重定向并将指针传递给o2,如下所示:

if(msg.message=6) then begin
  FreeAndNil(PObject(msg.lParam)^);
  Exit;
end;

...

PostThreadMessage(l.ThreadID, 6, 0, LPARAM(@my.o2));

您不需要FreeMyObject,只需直接致电FreeAndNil即可。而且您不需要在消息中传递实例。

我希望你的真实代码不像这样奇怪! ; - )

答案 1 :(得分:3)

如果您想FreeAndNil只发送对象引用Integer(my.o2)的对象是不够的 - 您需要Integer(@my.o2)。您还应该在代码中进行相应的更改。

由于您的代码难以调试,我编写了一个简单的演示来了解必要的代码更改:

type
  PObject = ^TObject;

procedure FreeObj(PObj: PObject);
var
  Temp: TObject;

begin
  Temp:= PObj^;
  PObj^:= nil;
  Temp.Free;
end;

procedure TForm17.Button1Click(Sender: TObject);
var
  Obj: TList;
  PObj: PObject;

begin
  Obj:= TList.Create;
  PObj:= @Obj;
  Assert(Obj <> nil);
  FreeObj(PObj);
  Assert(Obj = nil);
end;

答案 2 :(得分:1)

这是发生了什么:

程序启动。初始化运行并向线程发送一条消息,该线程在传入的引用上调用FreeAndNil。这将传入的引用设置为 nil ,但它不会设置对象将o2保留为 nil 的字段。这是一个不同的参考。

然后在析构函数中,由于该字段不是 nil ,它会尝试再次释放它并且您获得双重自由错误(无效的指针操作异常)。由于您在析构函数中引发了异常,因此TMyClass永远不会被破坏,并且会从内存泄漏。

如果要执行此操作,请将某种类型的标识符传递给FreeMyObject而不是引用。像整数2或字符串o2。然后让FreeMyObject使用此值来查找它应该调用FreeAndNil的内容。 (如果您使用的是Delphi 2010或更高版本,那么使用RTTI非常容易。)这是一项更多的工作,但它可以解决您所看到的错误。