2008-10-24 4 views
24

Warum gibt die Funktion Assigned() in Delphi immer True zurück, nachdem ich den Destruktor aufgerufen habe?Warum werden Delphi-Objekte auch nach dem Aufruf von .Free zugewiesen?

Der folgende Beispielcode schreibt "sl ist immer noch zugewiesen" an die Konsole.

Allerdings kann ich FreeAndNil (sl) anrufen; und es wird nicht zugewiesen.

Ich habe in Delphi für eine Weile programmiert, aber das hat nie Sinn für mich gemacht.

Kann jemand erklären?

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. 

Ich versuchte, die VCL-Operationen zu vergleichen ... FreeAndNil ist kurz und süß und macht Sinn:

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

Aber TObject.Free ist in mysteriösem Assembler, was ich nicht verstehe:

+9

Diese Frage zeigt, wie Programmierer Variablennamen conflate könnte, die in einem Rahmen sind, mit Objekten, die onthe Haufen existieren. Das Objekt ist tatsächlich Speicher auf dem Heap, und Free gibt diesen Speicher auf dem Heap frei, aber es ist nicht möglich, dass diese Methode die lokale Variable löscht, die eine REFERENCE für das Objekt enthält, die jedoch NICHT das Objekt selbst ist. Obwohl die Pointer-Semantik in Delphi-Objektreferenzen, die Zeiger auf Objekte sind, größtenteils verborgen ist, folgt hier ein Fall, bei dem die zugrundeliegende Zeigerimplementierung durchleckt. –

Antwort

36

Wenn Sie sl.Free verwenden, wird das Objekt freigegeben, aber die Variable sl zeigt immer noch auf den jetzt ungültigen Speicher.

Verwenden Sie FreeAndNil (sl), um das Objekt freizugeben und den Zeiger zu löschen.

By the way, wenn Sie das tun:

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; 
+0

Sind Sie * sicher * es ist threadsafe? vmtDestroy könnte leicht zweimal am selben Objekt aufgerufen werden, also hoffentlich * ist * threadsafe. – Roddy

+5

Wie in der ursprünglichen Frage geschrieben, ist FreeAndNil() unglücklicherweise nicht * threadsafe, weil zwei Threads jeweils den Zeiger kopieren könnten, beide dann fortfahren, um das Objekt freizugeben. FreeAndNil() sollte wirklich Interlocked Variablen Access (http://msdn.microsoft.com/en-us/library/ms684122(VS.85).aspx) verwenden. –

+11

Der Aufruf von FreeAndNil thread-safe maskiert wirklich eine ansonsten gute Antwort. Wenn Sie sogar an einem Punkt sind, an dem Sie Thread-Sicherheit BENÖTIGEN, während Sie ein gemeinsames Objekt freigeben, haben Sie größere Probleme in Ihrem Code, als Sie jemals für FreeAndNil hoffen könnten. –

13

Delphi VCL 'Objekte' sind eigentlich Zeiger immer auf Objekte, aber dieser Aspekt wird in der Regel von Ihnen verborgen. Wenn Sie nur das Objekt freigeben, bleibt der Zeiger hängen, Sie sollten stattdessen FreeAndNil verwenden.

Der „Mysterious Assembler“ übersetzt in etwa:

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

Weil Freie Kontrollen für NIL zuerst, es ist sicher FreeAndNil mehrmals aufrufen ...

+3

... und setzen Sie das DL-Register auf 1, um zu markieren, dass Destroy mit dieser Methode aufgerufen wurde, und rufen Sie die zugehörige AfterDestruction-Methode auf. –

3

Die Freie Methode TObject wie das ist " lösche den Operator "in C++. Wenn Sie frei wählen, wird zuerst die Destroy-Funktion aufgerufen und dann wird der Speicherblock freigegeben, der dem Objekt zugewiesen wurde. Standardmäßig wird der Zeiger auf den Speicher dann nicht auf Null gesetzt, da dies einen Befehl verbraucht.

In den meisten Fällen muss der Zeiger nicht auf Null gesetzt werden, da dies in den meisten Fällen keine Rolle spielt. Manchmal ist es jedoch wichtig und Sie sollten nur den Zeiger für diese Fälle Null.

Zum Beispiel. In einer Funktion, in der ein Objekt erstellt und dann am Ende der Funktion freigegeben wird, ist es nicht sinnvoll, die Variable auf Null zu setzen, da nur CPU-Zeit verschwendet wird.

Aber für ein globales Objekt oder Feld, auf das später möglicherweise wieder verwiesen wird, sollten Sie es auf Null setzen. Verwenden Sie FreeAndNil oder setzen Sie den Zeiger auf Null, egal. Aber halten Sie sich von Nullsetzungsvariablen fern, die standardmäßig nicht auf Null gesetzt werden müssen.

1

Wir haben einfache Regeln:

  1. Wenn Sie Assigned() zu überprüfen, verwenden möchten, wenn ein Objekt Obj bereits oder nicht erstellt wird, dann sicher, dass Sie verwenden FreeAndNil(Obj) es freizugeben.

  2. Assigned() sagt nur, wenn eine Adresse zugewiesen ist oder nicht.

  3. Der lokalen Objektreferenz wird immer eine Mülladresse (irgendeine zufällige Adresse) zugewiesen, so ist es gut, sie auf Null zu setzen, bevor Sie sie verwenden.

Beispiel: (Dies ist nicht der vollständige Code)

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