Почему есть утечка памяти и как ее исправить? - PullRequest
5 голосов
/ 07 августа 2011
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» фактически освобождает И создает экземпляр, используя парадигму полиморфизма, но здесь это не нужно. Я только хочу отметить, что дизайн должен оставаться прежним.

Теперь вопрос и проблема - почему это происходит и как это исправить? Кажется, «если Назначено (o2)» не подходит.

Что я думаю: отправка указателя на my.o2 освобождает и nil o2, и я пытаюсь это сделать, но я не смог преобразовать указатель на объект в обработчике сообщений, понятия не имел почему.

Кто-нибудь может помочь? Спасибо

Ответы [ 3 ]

6 голосов
/ 07 августа 2011

Вы освобождаете 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 021 *

3 голосов
/ 07 августа 2011

Если вы хотите 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;
1 голос
/ 07 августа 2011

Вот что происходит:

Программа запускается. Инициализация запускается и отправляет сообщение в поток, который вызывает FreeAndNil для передаваемой ссылки. Это устанавливает передаваемую ссылку равной nil , но не устанавливает поле объекта, содержащее o2 до ноль . Это другая ссылка.

Затем в деструкторе, так как поле не является nil , оно пытается освободить его снова, и вы получаете ошибку двойного освобождения (недопустимое исключение операции указателя). Поскольку вы создали исключение в деструкторе, TMyClass никогда не будет уничтожен, и вы получите утечку памяти из него.

Если вы хотите сделать это правильно, передайте идентификатор некоторого типа в FreeMyObject вместо ссылки. Как целое число 2 или строка o2. Затем пусть FreeMyObject использует это значение для поиска того, что должно вызывать FreeAndNil (Если у вас Delphi 2010 или более поздняя версия, это довольно легко сделать с помощью RTTI.) Это немного больше работы, но она исправит ошибки, которые вы видите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...