【问题标题】:Why are Delphi objects assigned even after calling .Free?为什么调用 .Free 后还要分配 Delphi 对象?
【发布时间】:2010-09-19 02:24:36
【问题描述】:

在Delphi中,为什么调用析构函数后Assigned()函数还是返回True?

下面的示例代码会将“sl is still assigned”写入控制台。

但是,我可以调用 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;

【问题讨论】:

  • 这个问题展示了程序员如何将一个范围内的变量名与堆上存在的对象混为一谈。该对象实际上是堆上的内存,并且 Free 释放堆上的内存,但是该方法不可能擦除包含对对象的引用的局部变量,但它不是对象本身。尽管作为对象指针的 delphi 对象引用中的指针语义大部分是隐藏的,但这是底层指针实现泄漏的一种情况。

标签: delphi


【解决方案1】:

我们有简单的规则:

  1. 如果您想使用Assigned() 来检查对象Obj 是否已经创建, 然后确保使用FreeAndNil(Obj) 释放它。

  2. Assigned() 只说明是否分配了地址。

  3. 本地对象引用总是被分配一个垃圾地址(一些随机地址),所以在使用它之前最好将它设置为nil。

示例:(这不是完整的代码)

{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;

【讨论】:

    【解决方案2】:

    TObject 的 Free 方法就像 C++ 中的“删除操作符”。调用 free 将首先调用 Destroy 函数,然后它会释放为对象分配的内存块。默认情况下,指向内存的指针不会设置为零,因为这会用完一条指令。

    在大多数情况下,正确的做法是不要将指针设置为零,因为在大多数情况下这无关紧要。但是,有时它很重要,因此对于这些情况,您应该只使用 nil 指针。

    例如。在创建对象然后在函数末尾释放的函数中,将变量设置为零是没有意义的,因为这只是在浪费 CPU 时间。

    但是对于以后可能再次引用的全局对象或字段,您应该将其设置为零。使用 FreeAndNil 或只是自己将指针设置为 nil,没关系。但不要将默认情况下不需要归零的变量归零。

    【讨论】:

      【解决方案3】:

      如果使用 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;
      

      【讨论】:

      • 确定它是线程安全的吗? vmtDestroy 可以很容易地在同一个对象上调用两次,所以希望 is 是线程安全的。
      • 正如所写,在原始问题中,不幸的是,FreeAndNil() 不是 线程安全的,因为两个线程可以各自复制指针,然后都继续释放对象。 FreeAndNil() 应该真正使用互锁变量访问 (msdn.microsoft.com/en-us/library/ms684122(VS.85).aspx)。
      • 调用 FreeAndNil 线程安全确实破坏了一个很好的答案。如果您甚至认为在释放共享对象时需要线程安全,那么您的代码中的问题比您希望 FreeAndNil 解决的问题更大。
      • FreeAndNil 不会将 nil 分配给所有其他持有同一对象引用的变量。它只清除作为其参数传递的指针。结果,可以同时分配和取消分配对象,如Assigned(ObjectRef1) <> Assigned(ObjectRef2) ... :)
      • 感谢您删除关于线程安全的错误声明。现在这是一个很好的答案。
      【解决方案4】:

      Delphi VCL 的“对象”实际上总是指向对象的指针,但是这方面通常对您隐藏。仅释放对象会使指针悬空,因此您应该改用 FreeAndNil。

      “神秘的汇编程序”大致翻译为:

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

      因为 Free 首先检查 NIL,所以多次调用 FreeAndNil 是安全的...

      【讨论】:

      • ...并将DL寄存器设置为1,以标记使用该方法调用了Destroy并调用关联的AfterDestruction方法。
      猜你喜欢
      • 1970-01-01
      • 2016-04-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-21
      相关资源
      最近更新 更多