2010-08-18 15 views
11

Ich habe A case against FreeAndNil gelesen, verstehe aber immer noch nicht, warum ich diese Methode nicht in einem Klassendestruktor verwenden kann? Kann mir jemand erklären.Warum sollte ich Free und nicht FreeAndNil in einem Destruktor verwenden?

Update: Ich denke, der Kommentar von Eric Grange war am nützlichsten für mich. Die Verbindung zeigt, dass dies nicht offensichtlich ist, wie man damit umgeht, und es ist hauptsächlich eine Frage des Geschmacks. Auch die Methode FreeAndInvalidate war nützlich.

+0

fragte Sie eine schöne Frage hier. – Ampere

Antwort

14

Das Problem dabei ist, dass viele scheinen FreeAndNil als einig magische Kugel zu verwenden, die die geheimnisvollen Absturz Drachen töten werden. Wenn FreeAndNil() in verwendet der Destruktor scheint einen Absturz zu lösen oder andere Speicherbeschädigung Probleme, dann sollten Sie tiefer graben in die wahre Ursache. Wenn ich das sehe, ist die erste Frage , warum ist das Instanzfeld zugegriffen wird, nachdem diese Instanz zerstört wurde? Das weist normalerweise auf ein Designproblem hin.

Es argumentiert, dass es das eigentliche Problem versteckt, das Sie haben. Es muss bedeuten, dass Ihr Code auf Eigenschaften/Felder/Methoden eines Objekts zugreift, das bereits zerstört ist (der Destruktor wird aufgerufen). Anstatt also das eigentliche Problem mit FreeAndNil zu verstecken, sollten Sie das zugrunde liegende Problem lösen.

Dieser Code unten stürzt nicht ab, wenn Sie FreeAndNil PropertyA im Destruktor von SomeObject verwenden würden. Aber es verbirgt das eigentliche Problem, dass SomeObject verwendet wird, nachdem es zerstört wurde. Es ist besser, dieses Designproblem (Zugriff auf zerstörte Objekte) zu lösen, statt es zu verstecken.

SomeObject.Free; // Destructor called 
if Assigned(SomeObject.PropertyA) then // SomeObject is destroyed, but still used 
    SomeObject.PropertyA.Method1; 

EDIT

Auf dem anderen Fall könnte man argumentieren, dass, wenn FreeAndNil nicht verwendet wird, würde der Code nicht entweder zum Absturz bringen. Selbst wenn das Objekt zerstört wird, wird der Speicher möglicherweise nicht wiederverwendet und alle Strukturen können intakt sein. Der obige Code könnte sogar ohne Probleme laufen, wenn Free verwendet wird, um PropertyA anstelle von FreeAndNil zu zerstören.

Und wenn FreeAndNil verwendet wurde, um SomeObject zu zerstören, würden Sie auch das eigentliche Problem sehen, egal, was der Code im Destruktor ist.

Also obwohl ich dem Argument zustimme, dass es den echten Designfehler verbergen könnte und nicht FreeAndNil in Destruktoren verwenden würde, ist es keine magische Kugel, solche Designfehler zu entdecken.

+0

Das Problem, dass die Verwendung von FreeAndNIL ignoriert, ist, wenn Ihr Destruktor auf eine Ausnahme trifft und ein unzerstörtes Objekt zurücklässt, das möglicherweise einem weiteren Zerstörungsversuch unterzogen werden könnte. In diesem Fall sollten bereits Free-Objekte natürlich nicht wieder frei sein. FreeAndNIL schützt vor unvollständigen Destruktoren. Es gibt ein Argument, dass ein unvollständiger Destruktor auch schlechtes Design widerspiegelt, aber leider haben Sie nicht immer genau die Kontrolle über das, was in Ihren Destruktoren passieren wird (zB wenn Sie andere Objekte zerstören, deren Destruktorverhalten Sie nicht direkt steuern oder einfach ändern) – Deltics

+4

Zum Schutz können Sie etwas wie 'FreeAndInvalidate' (http://delphitools.info/2010/02/06/dont-abuse-freeandnil-anymore/) verwenden, um den Verweis ungültig zu machen und nicht nur NULL, so dass alle weiteren Versuche Der Zugriff auf das Objekt löst eine Ausnahme aus. –

+0

Danke für den Link. Das gibt mir einen Einblick. –

7

Das Problem ist relativ einfach zu erklären, und der Streit um dieses Problem ist subjektiver als objektiv. Die Verwendung von FreeAndNil ist einfach nicht nötig, wenn die variable Referenz auf das Objekt freigegeben wird den Gültigkeitsbereich verlassen wird:

procedure Test; 
var 
    LObj: TObject; 
begin 
    LObj := TObject.Create; 
    try 
    {...Do work...} 
    finally 
    //The LObj variable is going out of scope here, 
    // so don't bother nilling it. No other code can access LObj. 

    //FreeAndNil(LObj); 
    LObj.Free; 
    end; 
end; 

In dem obigen Code-Schnipsel, nilling die LObj Variable sinnlos wäre, für den gegebenen Grund. Wenn jedoch eine Objektvariable während der Lebensdauer einer App mehrmals instanziiert und freigegeben werden kann, muss überprüft werden, ob das Objekt tatsächlich instanziiert ist oder nicht. Die einfache Möglichkeit, dies zu überprüfen, ist, ob der Objektverweis auf nil gesetzt wurde. Um diese Einstellung auf nil zu erleichtern, wird die Methode die Ressourcen freigeben und nil für Sie festlegen.Dann können Sie im Code überprüfen, ob das Objekt entweder mit LObj = nil oder Assigned(LObj) instanziiert ist.

Der Fall, ob .Free oder FreeAndNil() in Objekt Destruktoren zu verwenden, ist eine Grauzone, aber zum größten Teil, sollte .Free sicher sein, und in dem destructor sollte die Verweise auf Unterobjekte nilling unnötig. Es gibt verschiedene Argumente dafür, wie mit Ausnahmen in Konstruktoren und Destruktoren umzugehen ist.

Jetzt Aufmerksamkeit schenken: wenn Sie es vorziehen, ob zu wählen, und wählen .Free oder FreeAndNil() auf die besonderen Umstände oben umrissenen abhängig zu verwenden, das ist in Ordnung, aber beachten Sie, dass die Kosten eines Fehlers aufgrund nicht nilling einen befreit Objektverweis auf das anschließend zugegriffen wird, kann sehr hoch sein. Wenn auf den Zeiger später zugegriffen wird (Objekt freigegeben, aber Referenz nicht auf Null gesetzt), kann es passieren, dass Sie Pech haben und die Erkennung von Speicherbeschädigung viele Codezeilen entfernt von dem Zugriff auf die Referenz für freigegebene Objekte, die nicht freigegeben sind. Diese Art von Fehler kann sehr lange dauern, um zu reparieren, und ja, ich weiß, wie FastMM zu verwenden ist.

Deshalb ist es für einige Leute, mich eingeschlossen, Gewohnheit geworden (vielleicht eine faule), alle Objektzeiger einfach zu vernichten, wenn sie befreit sind, selbst wenn das Nilling nicht unbedingt notwendig ist.

+0

Sie behandeln einen einzelnen Fall, in dem Sie eine winzige Prozedur haben, bei der die Variable den Gültigkeitsbereich verlässt. Es gibt mehr als das. Aber +1, ich stimme der Verwendung von FreeAndNil zu. – Ampere

+0

@Altar: Ich habe dieses Beispiel ausgewählt, weil es die beste Situation bietet, in der argumentiert werden kann, dass "Free" besser ist als "FreeAndNil". Alle anderen Situationen sind weniger präzise. Um es nochmals zu sagen: Ich * immer * nil meine Objektreferenzen, wenn ich Objektinstanzen zerstöre, so dass ich nie darüber nachdenken muss, ob ich es muss oder nicht. –

5

Ich neigte dazu, ziemlich oft (aus welchem ​​Grund auch immer) FreeAndNil zu verwenden, aber nicht mehr. Was mich dazu brachte, damit aufzuhören, hängt nicht damit zusammen, ob die Variable danach nil sein muss oder nicht. Es bezieht sich auf Codeänderungen, insbesondere auf Typänderungen von Variablen.

Ich wurde mehrmals gebissen, nachdem ich den Typ einer Variablen von TSomething in einen Schnittstellentyp ISomething geändert habe. FreeAndNil beklagen nicht und macht glücklich seine Arbeit auf der Schnittstelle Variable. Dies führte manchmal zu mysteriösen Abstürzen, die nicht sofort an den Ort zurückverfolgt werden konnten, an dem sie stattfanden und einige Zeit brauchten, um sie zu finden.

Also wechselte ich wieder zu Free aufrufen. Und wenn ich es für notwendig halte, setze ich die Variable anschließend explizit auf nil.

3

i erlegt eine Stackoverflow Frage spricht über FreeAndNil und FreeAndInvalidate zu erwähnen, dass der Microsoft Security Development Lifecycle jetzt recommends something similar to FreeAndInvalidate:

Im Lichte der SDL Empfehlung oben - und eine Reihe von echten Bugs im Zusammenhang mit Wiederverwendung von Veraltete Verweise auf gelöschte C++ - Objekte ...

Die offensichtliche Wahl des Sanitisierungswerts ist NULL. Allerdings gibt es Nachteile: Wir wissen, dass eine große Anzahl von Anwendungsabstürzen auf NULL-Zeigerdereferenzen zurückzuführen ist. Wenn Sie NULL als Bereinigungswert auswählen, bedeutet dies, dass neue Abstürze, die durch diese Funktion eingeführt werden, weniger wahrscheinlich für Entwickler sind, als dass sie eine richtige Lösung benötigen - dh eine ordnungsgemäße Verwaltung von C++ - Objektlebensdauern und nicht nur eine NULL-Prüfung, die das unmittelbare Symptom unterdrückt .

Auch die Überprüfung auf NULL ist ein gängiges Code-Konstrukt, was bedeutet, dass eine vorhandene Überprüfung auf NULL zusammen mit NULL als Sanitizierungswert ein echtes Speichersicherheitsproblem verstecken könnte, dessen Ursache wirklich adressiert werden muss.

Aus diesem Grund wir 0x8123 als sanitization Wert gewählt haben - von einem Betriebssystem Perspektive dies in der gleichen Speicherseite als Nulladresse (NULL) ist, aber eine Zugriffsverletzung bei 0x8123 wird besser in den Entwickler stehen als Ich brauche eine genauere Aufmerksamkeit.

Also im Grunde:

procedure FreeAndInvalidate(var obj); 
var 
    temp : TObject; 
const 
    INVALID_ADDRESS = $8123; //address on same page as zero address (nil) 
begin 
    //Note: Code is public domain. No attribution required. 
    temp := TObject(obj); 
    Pointer(obj) := Pointer(INVALID_ADDRESS); 
    temp.Free; 
end; 
Verwandte Themen