Pamięć - zwalnianie i wycieki

0

Witam
Mam taki problem:
Tłumaczę właśnie część kodu z VB do delphi (kilka class) i w miare wszystko się udaje ale chciałbym sprawdzić zwalnianie pamięci.
Jest to temat który bardzo słabo znam (w literaturze mi dostepniej tez jest lakonicznie - że trzeba itp).

Z tego co zrozumiałem po uzyciu .create muszę potem użyć .free , natomiast używająć SetLenght(xxx,10) trzeba użyć finalize.

Słyszałem że są jakiej programiki do badania wycieków pamięci.

Dla chcących mi pomóc dokładniej opisuję sytuacje:

  • 20 class (każda w osobnym unicie) ,
  • średnio po 40 procedur, funkcji i property w każdej klasie.

(przepatrzyłem archiwum i nic dokładnie nie znalazłem o dokładnej mozliwości analizowania pamieci)

0

jest kilka zasad, które trzeba przestrzegać

  1. należy zwolnić wszystko, co sam tworzysz, czyli jak robisz Create to należy potem zrobić Free (zamiast Free lepiej zrobić FreeAndNil(obiekt))
  2. jeśli gdzieś używasz tablic dynamicznych to jeśli są już nie potrzebne należy je zwolnić przez SetLength(tablica, 0)
    do testowania jest pod Delphi DUnit albo MemProof a tu masz opisane wycieki pamięci i jak im zapobiegać w różnych wersjach Delphi i BCB
0

Wielkie dzięki - super odp - takiej odpowiedzi chciałem i tego szukałem - jak bedziesz około Olsztyna masz krate browara. [browar]

0
Misiekd napisał(a)

jest kilka zasad, które trzeba przestrzegać
2. jeśli gdzieś używasz tablic dynamicznych to jeśli są już nie potrzebne należy je zwolnić przez SetLength(tablica, 0)

... lub przez tablica := nil;

Jeśli tablica dynamiczna jest zmienną lokalną w procedurze/funkcji, to nie trzeba jej zwalniać - zajmie się tym delphiowy menedżer pamięci - podobnie jak nie trzeba "czyścić" lokalnych zmiennych typu String, OleVariant lub typów interfejsowych (IUnknown i pochodne). Tego typu zmiennych lokalnych nie trzeba również inicjować przed użyciem - są automatycznie inicjowane na wartość "pustą" przez kod wygenerowany przez kompilator.

0

tablice nalezy zwalniac przez
Finalize()
wtedy gdy nie sa deklarowane jako lokalne oraz gdy zajmowane przez nie zasoby sa kluczowe dla aplikacji.

0

Tablice dynamiczne trzeba zwalniać? Specjaliści, a stringa też zwalniacie? :D

Słyszeliście o czymś takim jak licznik odwołań? :P

0

geniuszu, oczywiście sam nic nie zwalniasz w dynamicznej tablicy obiektów?
chyba nie wiesz co to licznik odwołań, nie ma on do rzeczy nic przy ręcznie tworzonych obiektach, jeśli obiektu nigdzie nie niszczysz, to co mu pomoże licznik, co?
a może język programowania pomyliłeś? java? c#?

0

ŁF - on ma rację. tablicy dynamicznej jako takiej nie trzeba zwalniać. Obiekty, które są w niej przechowywane, o ile nie są prymitywami - tak, ale tego z kolei nie załatwi się instrukcjami podanymi przez Miśka i kolegów. Wyobraź sobie, że do tablicy dynamicznej pakujesz wskazania do komponentów utworzonych w taki sposób:

Tab[i] := TJakisKomponent.Create(Application);

Niech Cię ręka boska broni zwalniać tu COKOLWIEK - o zwolnienie obiektów zatroszczy się Application, a tablica zostanie usunięta samoczynnie.

Proponuję potestować trochę program pod D2006 wzwyż z włączoną opcją raportowania błędów lub - w przypadku niższych wersji Delphi - z podłączonym FastMM. Wnioski są jednoznaczne - tablic dynamicznych NIE TRZEBA zwalniać.

0
TBSO napisał(a)

Wyobraź sobie, że do tablicy dynamicznej pakujesz wskazania do komponentów utworzonych w taki sposób:

Tab[i] := TJakisKomponent.Create(Application);

Niech Cię ręka boska broni zwalniać tu COKOLWIEK - o zwolnienie obiektów zatroszczy się Application, a tablica zostanie usunięta samoczynnie.

przykład banalny:

var
  a : array of TObject;
...

procedure TForm1.Button1Click(Sender: TObject);
var
  i : integer;
begin
  setlength(a,100000);
  for i := 0 to length(a)-1 do a[i] := TObject.Create();
  a := nil;
end;

poklikaj w buttona i zobacz, czy pamięć na pewno się zwalnia. co do stringów i innych typów nie będących obiektami, w tym tablic dynamicznych - masz rację, Delphi zwalnia pamięć za mnie.

0

Przeoczyłeś że tam było ".Create(Application)"?

Jak napisałem - zwalnianie obiektów przypisanych do elementów tablicy to jedna rzecz, to trzeba rozpatrywać indywidualnie - obiekty posiadające właściciela są zwalniane przez niego. Natomiast sama tablica dynamiczna zwalniania nie wymaga, no ale tutaj się już chyba zgodziliśmy :)

0

masz rację, ale z jednym dużym ALE. Application jest globalne dla całego programu, jeśli będzie ownerem, to spowoduje, że pamięć zostanie zwolniona dopiero przy zakończeniu aplikacji. w zasadzie to samo tyczy się wszystkich formatek i ich elementów, tak więc jest to metoda na zrobienie wycieków pamięci. co prawda będą posprzątane przy zamykaniu aplikacji, ale kogo to wtedy obchodzi?
tak więc dalej będę obstawać przy założeniu, że KAŻDY obiekt utworzony ręcznie POWINIEN być zwolniony ręcznie, a już zawsze, gdy kod pisze programista-amator, który nie panuje nad tym, kiedy jaki owner jest niszczony.

poza tym - założę się, że nawet nie sprawdziłeś w praktyce tego, co pisałeś. a ja to zrobiłem. zmień w moim kodzie TObject na TForm1, poklikaj na buttona i sam sprawdź, co się dzieje z pamięcią i uchwytami.

0
ŁF napisał(a)

masz rację, ale z jednym dużym ALE. Application jest globalne dla całego programu, jeśli będzie ownerem, to spowoduje, że pamięć zostanie zwolniona dopiero przy zakończeniu aplikacji. w zasadzie to samo tyczy się wszystkich formatek i ich elementów, tak więc jest to metoda na zrobienie wycieków pamięci. co prawda będą posprzątane przy zamykaniu aplikacji, ale kogo to wtedy obchodzi?
tak więc dalej będę obstawać przy założeniu, że KAŻDY obiekt utworzony ręcznie POWINIEN być zwolniony ręcznie, a już zawsze, gdy kod pisze programista-amator, który nie panuje nad tym, kiedy jaki owner jest niszczony.

poza tym - założę się, że nawet nie sprawdziłeś w praktyce tego, co pisałeś. a ja to zrobiłem. zmień w moim kodzie TObject na TForm1, poklikaj na buttona i sam sprawdź, co się dzieje z pamięcią i uchwytami.

Pudło - sprawdziłem. Warstwy sieci neuronowych łączyłem przy pomocy tablic dynamicznych, i miałem mnóstwo okazji żeby przetestować. Wyjście jednej było jednocześnie wejściem drugiej, i w tym momencie fakt, że we i wy były tą samą tablicą, tylko z licznikiem odwołań - oszczędzał mi mnóstwo roboty.

warstwa[i].output := JakasDynamicznaTablicaIntegerow;
warstwa[i+1].Input := JakasDynamicznaTablicaIntegerow;

Przestawienie czegokolwiek na wyjściu przez warstwę i automatycznie zmieniało wejscie w warstwie i+1. Nawiasem mówiąc - lepiej to zrobic na obiektach, ale na czas testów jest to wyśmienite rozwiązanie.

Co do zwalniania przez TApplication - rany, czepiasz się. Podałem DOWOLNEGO ownera, żeby nie komplikować i nie pisać kawałków kodu. Wyobraź sobie inny kod, tym razem niemal z życia wzięty - stosuję podobne konstrukcje na codzień:

procedure DoSomething(SomeComponentCount: integer);
var
  frm: TSomeForm;
  SomeComponent: TSomeComponent;
  len: integer;
  DynArr: array of TSomeComponent;
  i: integer;
begin
  frm := TSomeForm.Create(Application);
  for i := 0 to SomeComponentCount - 1 do
  begin
    SomeComponent := TSomeComponent.Create(frm);
    SomeComponent.Parent := frm;
   {... tutaj ustawianie innych właściwości SomeComponent ...}
    len := Length(DynArr);
    SetLength(DynArr, len+1);
    DynArr[len] := SomeComponent;
  end;
end;

TSomeForm w zdarzeniu OnClose ustawia Action := caFree;

Jak widzisz - nic nie trzeba zwalniać, SomeComponent jest wywalane przez frm w chwili jej zamknięcia - i bardzo dobrze, bo o to chodzi.

0

W pośpiechu pominąłem instukcję frm.Show :)
No i - w tym konkretnym przypadku - nie ma sensu ustawiać w pętli co chwila rozmiaru DynArr, wystarczy przed pętlą dać SetLength(DynArr, SomeComponentCount). Ale reszta konstrukcji - mam nadzieję, że jasna?

0

nie zrozumielismy się. jeśli frm żyje sobie sobie tak długo, jak cała aplikacja, to tak samo długo żyją też jego dzieci. jeśli to wizualne kontrolki lub inne rzeczy wymagane do jego bieżącego działania, to wszystko ok. ale jeśli to jakieś zmienne wykorzystywane do przechowywania tymczasowych danych, to jest to w zasadzie wyciek pamięci. i to niewykrywany przez zewnętrzne narzędzia, bo przecież owner trzyma adresy danych do zwolnienia.

0

No cóż, faktycznie trochę inaczej rozumiem pojęcie wyciek pamięci, a forma MDI ma żyć z definicji tak długo, jak jej użytkownik każe. Tym niemniej - cała dyskusja zaczęła się od tego, czy trzeba zwalniać tablice dynamiczne (o elementach tablic dyskusja zaczęła się znacznie później). I w tym fragmencie dyskusji specjalisci_... miał rację - samych tablic jako takich zwalniać nie trzeba, ani przez SetLength, ani przez podstawienie nilem. Kogoś fantazja poniosła i tyle :)

// co do tematu z wątku - jak najbardziej się z Tobą zgadzam - Ł

0

Ale żeście się rozgadali. Po pierwsze jak duskutować o samoczynnym zwalnianiu, to pasowało by nadmienić jakich klas to dotyczy w przypadku Delphi - ktoś gotów pomyśleć że nie trzeba zwalniać np TStringList. A więc dotyczy to pochodnych TComponent (za helpem):

...
For components created programmatically, that is, not created in the form designer, call Create and pass in an owner components the AOwner parameter. The owner disposes of the component when it is destroyed. If the component is not owned, then use Free when it needs to be destroyed.
...

Po drugie, po co podawać jakieś skomplikowane przykłady skoro wystarczy prosty test:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TTest = class (TComponent)
  private
    fName : String;
  public
    constructor Create(aOwner : TComponent; aName : String);
    destructor Destroy; override;
  end;

var
  Form1: TForm1;
  t1, t2 : TTest;

implementation

{$R *.DFM}

{ TTest }

constructor TTest.Create(aOwner: TComponent; aName : String);
begin
  inherited Create(aOwner);
  fName := aName;
end;

destructor TTest.Destroy;
begin
  Application.MessageBox(PChar(fName + ' jest zwalniany'), nil, 0);
  inherited;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  t1 := TTest.Create(Application, 'Application');
  t2 := TTest.Create(nil, 'Bez ownera');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
//  t2.Free; // przy zakomentowanej linijce będzie wyciek pamięci
end;

end.

To tyle dla potwierdzenia słów jednego z was. Jak to działa, to jedna sprawa są jeszcze inne aspekty. To że coś jest automatycznie zwalniane OK, pytanie tylko czy jesteśmy świadomi kiedy ... i przykład książkowy:

"a gdyby tu było przedszkole w przyszłości ... i kontruktor alokował by bitmapę dwu-megową" - to by sobie żyła aż aplikacja/forma się nie zamknie, bez względu na to czy była by potrzeba czy nie.

Dlatego też IMHO najlepiej jest przyjąć zasadę tworzymy-zwalniamy, a konkretnie

t := ttest.create();
try
...
finally
freeandnil(t)
end;

Tak jest po prostu czytelniej. Wiem jest więcej kodu - ale IMHO tak jest lepiej.

Aha ktoś tam napisał że coś jest automatycznie inicjowane. Otóż w przypadku zmiennych globalnych czy pól klas OK, ale w przypadku zmiennych lokalnych (czy Integer, czy pochodnych TObject) - FALSE !!! Sprawdźcie sami jak nie wierzycie. Podstawowa zasada przy używaniu zmienych lokalnych jest taka żeby nadać im początkową wartość !!! Zresztą, jak ktoś czasem zwraca uwagę przed czym kompilator nas ostrzega, to tego typu informacja właśnie się tam znajdzie.

Pozdro

1 użytkowników online, w tym zalogowanych: 0, gości: 1