Różnice między Free, Destroy a FreeAndNil

Podczas pisania kodu często korzystamy z różnego rodzaju zmiennych, które dobrze jest zwolnić, gdy nie są już potrzebne. Delphi udostępnia kilka sposobów na zwalnianie pamięci po użytych zmiennych, są to: Destroy, Free i FreeAndNil.
Której z tych metod użyć i która jest najlepsza?

Na początek zobaczmy jak wygląda zawartość wymienionych procedur:

procedure FreeAndNil(var Obj);
var
  Temp: TObject;
begin
  Temp := TObject(Obj);
  Pointer(Obj) := nil;
  Temp.Free;
end;
procedure TObject.Free;
begin
  if Self <> nil then
    Destroy;
end;
destructor TObject.Destroy;
begin
end;

Po zawartości powyższych procedur można wysunąć proste wnioski:
FreeAndNil - zwalnia pamięć zajmowaną przez zmienną po czym przypisuje jej wartość równą nil. Parametr Obj musi być obiektem dziedziczącym z klasy TObject.
Free - przed wywołaniem destruktora (Destroy) sprawdza czy zmienna nie jest pusta, czy jest zainicjalizowana.
Destroy - destruktor zwalniający (niszczący) zmienną. Gdy zmienna nie jest zainicjalizowana wywołanie Destroy spowoduje błąd.
Warto przy okazji nadmienić, że destruktor jest specjalną metodą klasy, która wywoływana jest gdy instancja tej klasy jest "niszczona" czyli zwalniana jest pamięć zajmowana przez obiekt tej klasy.

Jak to wygląda w praktyce można zobrazować prostym przykładem:

procedure DestroyVsFreeDemo();
var mojaZmienna : TMojaKlasa;
begin
//zwalnianie niezainicjalizowanej zmiennej
FreeAndNil(mojaZmienna);
mojaZmienna.Destroy();
mojaZmienna.Free();
end;

Takie wywołanie FreeAndNil spowoduje błąd, ponieważ zmienna mojaZmienna typu TMojaKlasa nie jest zainicjalizowana i wykonywane na niej operacje wewnątrz procedury FreeAndNil spowodują błąd.
mojaZmienna.Destroy() również spowoduje błąd z tego samego powodu. Nie można wywołać destruktora obiektu, który nie jest zainicjalizowany.
mojaZmienna.Free() wywoła się bezbłędnie ponieważ w procedurze Free, jak już wspomniano, sprawdzane jest wcześniej czy zmienna jest zainicjalizowana, nie wskazuje na nil.

Dlaczego Free nie spowodowało błędu? Składnia procedury Free jest następująca: sprawdź czy obiekt nie wskazuje na nil czyli jeżeli obiekt wskazuje na jakiś adres w pamięci, to wywołaj destruktor tego obiektu.
Wynika więc z tego że niezainicjalizowany obiekt mojaZmienna typu TMojaKlasa musi wskazywać na jakiś adres w pamięci.
Aby to sprawdzić posłużmy się przykładem:

procedure DestroyVsFreeDemo();
var mojaZmienna : TMojaKlasa;
begin
if (mojaZmienna <> nil) then ShowMessage('Zmienna wskazuje na coś');
end;

Wniosek z powyższego kodu jest prosty. Delphi nadaje niezainicjalizowanym zmiennym typu referencyjnego przypadkowe wartości i na pewno nie jest to NIL.
Inicjalizowane są tylko zmienne globalne.

Prawidłowo działająca procedura powinna być zatem tak zbudowana:

procedure DestroyVsFreeDemo();
var mojaZmienna : TMojaKlasa;
begin
mojaZmienna := TMojaKlasa.Create();
try
  //jakieś operacje na zmiennej
finally
  if (mojaZmienna <> nil) then 
    FreeAndNil(mojaZmienna); //zmienna została zniszczona, wskazuje na wartość nil
end;
end;

WNIOSKI:
Jeżeli tylko mamy możliwość używamy FreeAndNil. Jeżeli chcemy aby zmienne po zniszczeniu wskazywały na nil używamy FreeAndNil.
Gdy nie mamy dostępu do FreeAndNil używamy Free. Nigdy bezpośrednio nie używamy Destroy. Destruktor wywoła się zawsze gdy użyjemy Free lub FreeAndNil.
 

Posłużmy się teraz innym przykładem:

procedure TMainForm.btn_TestFreeClick(Sender: TObject);
var
  bm: TBitmap;
begin
bm := TBitmap.Create;
try
  bm.LoadFromFile('C:\rysunek1.bmp');
finally
  bm.Free;
end;

//Assigned sprawdza czy zmienna bm jest równa Nil
if Assigned(bm) then
    bm.SaveToFile('C:\rysunek2.bmp')
  else
   ShowMessage('Nie można zapisać bitmapy ponieważ nie istnieje');
end;

W rezultacie program nie wyświetli komunikatu, że nie można zapisać bitmapy. Mimo że pamięć zajmowana przez zmienną bm została zwolniona, zmienna ta nie wskazuje na nil, ciągle wskazuje na jakiś adres w pamięci. W wyniku wywołania powyższej procedury na dysku C zostanie utworzony pusty plik rysunek2.bmp.
Wniosek: użycie FreeAndNil spowodowałoby zwolnienie pamięci zajmowanej przez bitmapę (bm) oraz przypisanie do zmiennej bm wartości nil. Funkcja Assigned zwróciłaby wówczas wartość FALSE i użytkownik zostałby poinformowany o niepowodzeniu odpowiednim komunikatem.

Komentarze

w zdaniu: 

Wniosek z powyższego kodu jest prosty. Delphi nadaje niezainicjalizowanym zmiennym domyślne wartości i na pewno nie jest to NIL.

Jest albo duże niedopowiedzenie, albo kłamstwo.
Otórz zmienne w funkcjach są alokowane na stosie i ich wartość zależy od tego co było wcześniej w tej pamięci czyli wartość może być przypadkowa (w tym może trafić się, że będzie to nil), Dlatego należy nadać jej wartość jeśli chcemy sprawdzać późniejszy stan.
Tworząc taką zmienną jako zmienna globalna będzie zainicjowana nil'em na 100%

Sprawdzanie czy MojaZmienna <> NIL jest chyba zbędne przy użyciu FreeAndNil. Jeśli MojaZmienna = NIL, wtedy nic się nie dzieje, ponieważ sprawdzanie to jest realizowane przez metodę Free w procedurze FreeAndNil. Jeśli MojaZmienna nie ma domyślnie zainicjowanej wartości NIL a nie użyto konstruktora celem utworzenia obiektu takie zwolnienie nieistniejącego jeszcze obiektu nie ma sensu, zresztą wyskoczy runtime error.

Tak zgadzam się z Panem, że to sprawdzenie jest zbędne. Poniższa procedura wykona się bez błędu:

procedure DestroyVsFreeDemo();
var mojaZmienna : TStringList;
begin
mojaZmienna := nil;
try
finally
    FreeAndNil(mojaZmienna); //zmienna została zniszczona, wskazuje na wartość nil
end;
end;

Co do drugiej części, przypisanie mojej zmiennej nil jest celowe aby taki wyjątek nie wyskoczył.

Jedna zasadnicza sprawa. Akurat w tym przykładzie mojaZmienna typu TStringList nie została w ogóle utworzona (nie użyto konstruktora). Tak naprawdę w mojej praktyce nie dbam o to czy zmienna ma domyślnie wartość NIL, zresztą zawsze używam nie FreeAndNil ale Free. Ale co jest istotne, jakikolwiek obiekt, przed jego użyciem musi zostać utworzony konstruktorem, dopiero później jest zwalniany za pomocą metody Free. Innymi słowy nie dopuszczam do sytuacji aby nie utworzony jeszcze obiekt był gdziekolwiek użyty.
Zresztą metoda Free sama w sobie sprawdza czy obiekt ma wartość NIL, jeśli tak to nic się nie dzieje (destruktor Destroy w ogóle nie zostanie wywołany). Myślę że powyższy przykład ma na celu jedynie zobrazować problematykę dotyczącą pewnych praktyk programistów, tutaj poza tym że do mojaZmienna zostanie przypisana wartość NIL, program tylko sprawdzi czy faktycznie mojaZmienna dostała taką wartość i na tym koniec bowiem zmienna ma wartość NIL a instrukcje w finally wykonają się niezależnie od tego czy instrukcje po try wykonają się z powodzeniem czy też nie.

Bardzo fajny i przydatny artykulik:) Thx!

Dodaj nowy komentarz

Filtrowany HTML

  • Adresy internetowe są automatycznie zamieniane w odnośniki, które można kliknąć.
  • Dozwolone znaczniki HTML: <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Znaki końca linii i akapitu dodawane są automatycznie.
  • Tekstowe buźki będą zamieniane na ich graficzne odpowiedniki.

Czysty tekst

  • Znaczniki HTML niedozwolone.
  • Adresy internetowe są automatycznie zamieniane w odnośniki, które można kliknąć.
  • Znaki końca linii i akapitu dodawane są automatycznie.
  • Tekstowe buźki będą zamieniane na ich graficzne odpowiedniki.
CAPTCHA
W celu potwierdzenia, że jesteś człowiekiem, prosimy o wykonanie poniższego zadania
Target Image