Почему объекты Delphi назначаются даже после вызова. Свободно?

В Delphi, почему функция Assigned() все еще возвращает True после вызова деструктора?

Нижеприведенный пример кода будет записывать "sl по-прежнему назначается" на консоль.

Однако я могу назвать FreeAndNil (sl); и он не будет назначен.

Я программировал в Delphi некоторое время, но это никогда не имело смысла для меня.

Может кто-нибудь объяснить?

program Project1;
{$APPTYPE CONSOLE}
uses SysUtils, Classes;

var
  sl : TStringList;

begin
  sl := TStringList.Create;
  sl.Free;
  if Assigned(sl) then
    WriteLn('sl is still assigned')
  else
    WriteLn('sl is not assigned');
end.

Я попытался сравнить операции VCL... FreeAndNil короткий и сладкий и имеет смысл:

procedure FreeAndNil(var Obj);
var
  P: TObject;
begin
  P := TObject(Obj);
  TObject(Obj) := nil;  // clear the reference before destroying the object
  P.Free;
end;

Но TObject.Free находится в таинственном ассемблере, которого я не понимаю:

procedure TObject.Free;
asm
        TEST    EAX,EAX
        JE      @@exit
        MOV     ECX,[EAX]
        MOV     DL,1
        CALL    dword ptr [ECX].vmtDestroy
@@exit:
end;

Ответ 1

Если вы используете sl.Free, объект освобождается, но переменная sl по-прежнему указывает на теперь недопустимую память.

Используйте FreeAndNil (sl), чтобы освободить объект и очистить указатель.

Кстати, если вы это сделаете:

var
  sl1, sl2: TStringList;
begin
  sl1 := TStringList.Create;
  sl2 := sl1;
  FreeAndNil(sl1);
  // sl2 is still assigned and must be cleared separately (not with FreeAndNil because it points to the already freed object.)
end;




procedure TObject.Free;
asm
    TEST    EAX,EAX
    JE      @@exit              // Jump to exit if pointer is nil.
    MOV     ECX,[EAX]           
    MOV     DL,1
    CALL    dword ptr [ECX].vmtDestroy  // Call cleanup code (and destructor).
@@exit:
end;

Ответ 2

Delphi VCL "объекты" на самом деле всегда указывают на объекты, но этот аспект обычно скрыт от вас. Просто освобождение объекта оставляет указатель, висящий вокруг, поэтому вместо него следует использовать FreeAndNil.

"Таинственный ассемблер" грубо переводит:

if Obj != NIL then
  vmtDestroy(obj);  // which is basically the destructor/deallocator.

Поскольку Free проверяет сначала NIL, он безопасно вызывать FreeAndNil несколько раз...

Ответ 3

Свободный метод TObject подобен "оператору удаления" в С++. Вызов бесплатно сначала вызовет функцию Destroy, а затем освободит блок памяти, который был выделен для объекта. По умолчанию указатель на память не устанавливается на ноль, потому что это будет использовать одну команду.

В большинстве случаев правильная вещь - не устанавливать указатель на ноль, потому что в большинстве случаев это не имеет значения. Однако иногда это важно, поэтому для этих случаев нужно указывать только указатель.

Например. В функции, где объект создается и затем освобождается в конце функции, нет смысла устанавливать переменную в ноль, поскольку это просто тратит время процессора.

Но для глобального объекта или поля, на которые можно ссылаться позже, вы должны установить его на ноль. Используйте FreeAndNil или просто установите указатель на нуль самостоятельно, не имеет значения. Но держитесь подальше от нулевых переменных, которые по умолчанию не нужно обнулять.

Ответ 4

У нас есть простые правила:

  • Если вы хотите использовать Assigned(), чтобы проверить, создан ли объект Obj или нет, затем убедитесь, что вы используете FreeAndNil(Obj), чтобы освободить его.

  • Assigned() указывает только, назначен ли адрес или нет.

  • Ссылка на локальный объект всегда присваивается адресу мусора (некоторый случайный адрес), поэтому полезно использовать его перед использованием.

Пример: (Это не полный код)

{Opened a new VCL application, placed a Button1, Memo1 on the form
Next added a public reference GlobalButton of type TButton
Next in OnClick handler of Button1 added a variable LocalButton 
Next in body, check if GlobalButton and LocalButton are assigned}

  TForm2 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    GlobalButton: TButton;
  end;

procedure TForm2.Button1Click(Sender: TObject);
var
  LocalButton: TButton;
begin
  if Assigned(GlobalButton) then  
    Memo1.Lines.Add('GlobalButton assigned');
  if Assigned(LocalButton) then  
    Memo1.Lines.Add('LocalButton assigned');
end;