为什么会出现内存泄漏以及如何修复它?

发布于 2024-11-28 11:36:08 字数 1408 浏览 0 评论 0原文

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”实际上使用多态性范例释放并创建一个实例,但这里不需要这样做。我只想指出设计应该保持不变。

现在问题和问题 - 为什么会发生以及如何解决它?看来“ifAssigned(o2)”不适合它。

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

有人可以帮忙吗?谢谢

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.

I used the message handler to illustrate my problem as is so you understand it. The real design is a lot more complicated. The function 'FreeMyObject' actually Frees AND creates an instance using polymorphism paradigm, but this here is not needed. I only want to point out that the design should stay the same.

Now the question and problem - why it happens AND how to fix it? It seems 'if Assigned(o2)' doesn't fit it.

What I think of: Sending a pointer to my.o2 would free and nil o2 and I tries to do so, but I couldn't convert from pointer to object in the message handler, got no idea why.

Could anybody give a hand? Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

情归归情 2024-12-05 11:36:08

您释放了 o2 两次。一次是消息的结果,一次是析构函数的结果。

您认为在调用 FreeMyObject 时将 o2 设置为 nil,但事实并非如此。实际上,您将 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 。并且您不需要在消息中传递实例。

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

You free o2 twice. Once as a result of the message and once from the destructor.

You think you are setting o2 to nil when you call FreeMyObject but you are not. You are in fact setting msg.lParam to 0.

o2 is a variable holding a reference to an object. You are passing the value of o2 and when you pass by value you cannot modify the variable whose value you passed. So you need to pass a reference to o2. To do so you need to add an extra level of redirection and pass a pointer to o2, like so:

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

...

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

You don't need FreeMyObject, you can just call FreeAndNil directly. And you don't need to pass an instance in the message.

I hope your real code isn't quite as weird as this! ;-)

感受沵的脚步 2024-12-05 11:36:08

如果您想要 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;

If you want to FreeAndNil an object sending just object reference Integer(my.o2) is not enough - you need Integer(@my.o2). You should also make corresponding changes in your code.

Since your code is difficult to debug I have written a simple demo to give an idea of necessary code changes:

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;
琴流音 2024-12-05 11:36:08

发生的事情是这样的:

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

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

如果您想正确执行此操作,请将某种类型的标识符传递给 FreeMyObject 而不是引用。例如整数 2 或字符串 o2。然后让 FreeMyObject 使用此值来查找应该调用 FreeAndNil 的内容。 (如果您有 Delphi 2010 或更高版本,则使用 RTTI 可以很容易地做到这一点。)这需要更多的工作,但它会修复您看到的错误。

Here's what's going on:

Program starts. Initialization runs and sends a message to the thread, which calls FreeAndNil on the reference that gets passed in. This sets the reference that gets passed in to nil, but it does not set the object field holding o2 to nil. That's a different reference.

Then in the destructor, since the field isn't nil, it tries to free it again and you get a double-free error (invalid pointer operation exception). Since you raised an exception in the destructor, the TMyClass never gets destroyed and you get a memory leak from it.

If you want to do this right, pass an identifier of some type to FreeMyObject instead of a reference. Like an integer 2, or a string o2. Then have FreeMyObject use this value to look up what it should be calling FreeAndNil on. (If you have Delphi 2010 or later, that's pretty easy to do with RTTI.) It's a little more work, but it will fix the errors you're seeing.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文