Ograniczenie array[] of TStringList

0

Witajcie potrzebuję informacji czy lazarus ma ograniczenia lub błąd co do dynamicznego tworzenia i zwalniania przez użycie tstringlis.free w tabilcy tstringlistów?
Chodzi o coś takiego:

lista[x]:=tstringlist.create;
lista[x]:=tstringlist.free;

A za dluższy czas:

If lista[x]=nil then lista[x]:=tstringlist.create;

Wyrzuca mi błąd gdy sprawdzam czy dana lista istnieje za pomocą nil…

3
  1. JAKI BŁĄD???????????????????????????????????????????????????????????????????????????????????????????????
  2. nigdzie nie nilujesz wpisu w tablicy po zwolnieniu listy
  3. ograniczeniem jest dostępna ilość pamięci operacyjnej
  4. w 99,9% takich przypadków problem jest pomiędzy krzesłem a klawiaturą
0

Przydał by się trochę dłuższy kawałek kodu

2
Windowbee napisał(a):

Wyrzuca mi błąd gdy sprawdzam czy dana lista istnieje za pomocą nil…

No nie, wyrzuci ci błędy w miejscu wywołania metody Free, czyli tu:

lista[x]:=tstringlist.free;

i o takiej treści:

Error: Only class methods, class properties and class variables can be accessed in class methods
Error: Only class methods, class properties and class variables can be referred with class references

Wywołujesz metodę Free tak jakby była metodą statyczną, a ta nią nie jest. Poza tym próbujesz jej użyć jako funkcji, a Free jest metodą proceduralną, więc nie zwraca rezultatu, który byś mógł wpisać do komórki tablicy. Cała masa błędów, a to przecież podstawy OOP, w dodatku komunikaty kompilatora informują cię o tym, co jest źle.

Poprawny kod powinien wyglądać tak:

// Tworzy instancję klasy listy i zapisuje ją do komórki.
Lista[x] := TStringList.Create();

// Niszczy instancję z zadanej komórki tablicy, ale nie modyfikuje referencji z tej komórki.
Lista[x].Free();

// Zeruje referencję, aby można było później ją testować za pomocą "if Lista[x] = nil".
Lista[x] := nil;

Opcjonalnie możesz użyć procedury FreeAndNil z modułu SysUtils, jeśli wolisz w jednej linijce mieć destrukcję instancji oraz jej znilowanie:

FreeAndNil(Lista[x]);
0

Ogromne dzięki !!!
O to chodziło zapewne.
Błędów nie wyrzucało widocznych bo to się dzieje w wątkach pobocznych obsługujących serwer i wiele połączeń tcp.
Żeby dotrzeć do tego co daje błąd to linijka po linijce w podejrzanych procedurach kładłem funkcję której wykonanie pokazywało mi do jakiego momentu kod działa prawidłowo i tego zwykłego free nie chciało przeskoczyć tak jak i create gdy myślalem ze zwolnione.
Dzięki :)🙏

2

osobiście nie używałbym array do tego tylko listy

uses Generics.Collections;
type
  TMyListofTStringList = TList<TStringList>;
0

@Windowbee
poniżej przykład jak można tego używać:

unit uDices;

interface

uses Generics.Collections;

type
  TAfterThrowDices = procedure of object;
  TAfterThrowDicesList = TList<TAfterThrowDices>;
  TDices = class
  strict private
      type
        TDice = class
        strict private
          vValue: SmallInt;
          vSelected: boolean;
        public
          property value: SmallInt read vValue;
          property selected: boolean read vSelected write vSelected;
          function ThrowDice(): SmallInt;
          constructor Create();
        end;
        TDicesList = TList<TDice>;
  strict private
    class var FAfterThrowDicesList: TAfterThrowDicesList;
    class var FDiceClassList: TDicesList;
    class constructor Create;
    class destructor Destroy;
  public
    class property Dices: TDicesList read FDiceClassList;
    class property AfterThrowList: TAfterThrowDicesList read FAfterThrowDicesList write FAfterThrowDicesList;
    class procedure ThrowAllDices();
  end;


implementation

uses Math;

class constructor TDices.Create;
var
  i: Integer;
begin
  Randomize;
  FDiceClassList := TDicesList.Create;
  FAfterThrowDicesList := TAfterThrowDicesList.Create;
  for i := 1 to 5 do
    FDiceClassList.Add(TDice.Create);
end;

class destructor TDices.Destroy;
begin
  FAfterThrowDicesList.Free();
  FDiceClassList.Free;
end;

class procedure TDices.ThrowAllDices;
var
  i: smallint;
begin
  //todo tthread
  for i := 0 to FDiceClassList.Count -1 do
    FDiceClassList[i].ThrowDice;
  for i := 0 to FAfterThrowDicesList.Count - 1 do
    FAfterThrowDicesList.Items[i];
end;

{ TDices.TDice }

constructor TDices.TDice.Create;
begin
  vValue := ThrowDice;
  vSelected := False;
end;

function TDices.TDice.ThrowDice: SmallInt;
begin
  vValue := RandomRange(1,7);
  Result := vValue;
end;


end.
0

czy .free i =nil po tym zwolni pamięć z array którą uprzednio zajmowała tstringlista? Czy zmieniać dynamicznie powiniennem array size także? Nie chciałbym zobaczyć za jakiś czas wycieku pamięci przez ten mój bład. Będzie mi do niego trudno wrócić gdy zapomnę co sprawiało problem.

1
Windowbee napisał(a):

czy .free i =nil po tym zwolni pamięć z array którą uprzednio zajmowała tstringlista?

Free zwalnia całą pamięć zajmowaną przez dane listy, więc tak — zwalnia obiekt i wszystko co ten obiekt sam alokował. Natomiast przypisanie nil do komórki tablicy nie ma nic wspólnego z czyszczeniem pamięci. Tym trzeba oznaczyć tę komórkę jako pustą, tak aby później można było określić, czy dana komórka zawiera listę czy nie.

Czy zmieniać dynamicznie powiniennem array size także?

Napisz to pytanie jeszcze raz, tym razem po polsku. :D

Nie chciałbym zobaczyć za jakiś czas wycieku pamięci przez ten mój bład.

A ja bym wolał, abyś zobaczył wyciek pamięci. O to właśnie chodzi, żebyś wiedział, jeśli coś złego dzieje się w kodzie. Używaj modułu HeapTrc, a zawsze dostaniesz informacje o tym, czy w programie jest jakiś wyciek czy nie.

Będzie mi do niego trudno wrócić gdy zapomnę co sprawiało problem.

Jeśli użyjesz HeapTrc, to on poinformuje cię o wszystkich wyciekach i dokładnie wskaże miejsca alokacji tych bloków, które wyciekły — łącznie z nazwą modułu, numerem linii alokacji, a nawet z zawartością tych bloków. Czyli wszystko co jest potrzebne, aby namierzyć wyciek w kilka sekund i go następnie naprawić.

0

Dzięki :) po polsku bardziej byłoby tak: czy rozmiar tablicy z tstringlistami powiniennem regulować w trakcie działania wątku/programu? Ustawiłem na sztywno rozmiar na 2000…. Nie osiągnę takiej ilości nigdy(raczej). TStringlist zajmują tablicę blisko początku do mniej więcej setki ,są zwalniane i na ich miejsce tworzone są inne. Raz jest 20 raz 80 obiektów typu tstringlsit. Stąd pytanie czy w trakcie działania programu modyfikować rozmiar tablicy stosownie do ilości zajętych - wykorzystywanych elementów. Jeśli nie są zajęte są .free i nilowane.

0

Już załatwione, pamięć nie cieknie dzięki @furious programming
Tablicę jako taką też wyregulowałem poprzez setlenght(array,x) tak że jest całkowicie odporna na duże wzrosty ilości potrzebnych tstringlistów, a gdy nie potrzeba jest przycinana. Nie ustawiam już rozmiaru tablicy statycznie w sekcji jej deklaracji.

0
Windowbee napisał(a):

czy rozmiar tablicy z tstringlistami powiniennem regulować w trakcie działania wątku/programu?

Możesz dynamicznie zmieniać rozmiar tablicy, ale możesz też tego nie robić. Wszystko zależy od przypadku.

Jeśli nie wiesz ile maksymalnie elementów trafi do danego kontenera, to nie wiadomo jak duży blok zaalokować, aby program nigdy nie przekroczył krytycznej liczby elementów. Wtedy korzysta się z takiego typu danych, który może być relokowany w trakcie działania programu — tablica dynamiczna czy ręcznie alokowane i relokowane bloki pamięci (np. GetMem i ReallocMem). To daje ci sam język, innych rozciągalnych typów danych, innych niż tablice dynamiczne, nie ma. ;)

Można też skorzystać z klas kontenerów, np. z list i jedną z nich jest TStringList — im więcej stringów dodasz do niej, tym więcej pamięci będzie samodzielnie alokowała. Żeby ograniczyć liczbę relokacji, lista ta (re)alokuje bufor z miejscem zapasowym. Początkowo wzrost jest wykładniczy, aby ostatecznie osiąść na relokacji bloku z dołożeniem pustego miejsca dla przyszłych 256 elementów.

Plusem takiego podejścia jest to, że bufor dla danych jest w razie potrzeby relokowany, więc może pomieścić dowolną liczbę elementów. Minusem jest to, że wykonywana co jakiś czas relokacja wymaga pozyskania nowego bloku od menedżera pamięci (lub od samego systemu operacyjnego), a cała aktualna zawartość musi zostać przekopiowana w nowe miejsce. To jest czasochłonne i utrudnia cache'owanie danych przez CPU. Coś za coś.

Natomiast jeśli już na etapie pisania kodu wiesz, że elementów nigdy nie będzie więcej niż np. 1000, to skorzystanie z kontenera o statycznym rozmiarze (np. ze zwykłej tablicy) jest bardzo dobrym rozwiązaniem. Miejsca nigdy nie braknie, a bufor zawsze jest w tym samym miejscu pamięci, więc ułatwia się jego cache'owanie. Minusem jest to, że używa się więcej pamięci niż w praktyce potrzeba, jednak jeśli są to kilobajty czy kilka megabajtów zapasu, to nie przeszkadza to w niczym.

Stąd pytanie czy w trakcie działania programu modyfikować rozmiar tablicy stosownie do ilości zajętych - wykorzystywanych elementów. Jeśli nie są zajęte są .free i nilowane.

Twoja tablica z miejscem dla 2000 list zajmuje 2000 * SizeOf(Pointer) bajtów, czyli dokładnie 16KB — no nie jest to ilość pamięci, która by była imponująco duża i zmuszała do szukania optymalizacji pod tym kątem.

Zobacz na to z innej strony — czy opłaca się nilować puste komórki tej tablicy? Żeby później użyć tej komórki ponownie, najpierw musisz sprawdzić czy jest ona pusta, a więc najpewniej szukasz pierwszej pustej komótki, iterując od poczatku tablicy.

Jeśli usunięcie którejś listy ciągów z tablicy nie może przesuwać pozostałych list, bo np. wątki operują na tej tablicy i używają indeksów, to taka tablica jest spoko. Natomiast jeśli usuwanie i dodawanie list ciągów do tablicy może modyfikować kolejność list w tablicy, to lepiej po prostu skorzystać z listy list — nową listę dodawać zawsze na koniec i pozwolić klasie kontenera zająć się również usuwaniem elementów. W takim przypadku, zamiast z tablicy, skorzystaj z generycznego TFPGObjectList z modułu FGL, a za typ elementów wstaw TStringList:

uses
  FGL;

type
  TListOfStringList = specialize TFPGObjectList<TStringList>;
0

Dzięki jeszcze raz.
Wątki operują na dodatkowej tstringliście (spoza tablicy) która przechowuje indeksy czyli nazwy potrzebnych tabel oraz w miejscu gdzie jest puste jest wpis „” indeksem jest pozycja na liście. Długość tej tstringlist jest identyczna jak
Array i jest razem z array modyfikowana. Szukanie wolnego miejsca do wygenerowania nowej tstringlisty w tablicy odbywa się przez indexof(‚
’), jeśli nie znajduje wolnego to powiększa o 1 array i tstringliste indeksów a w ostatni wiersz indeksów wpisuje *.
Wpisy ze środka listy indeksowej nigdy nie są kasowane tylko zastępowane przez *. Dzięki temu nie zmienia się kolejność poszczególnych indeksów. Kasowanie lub nie ma miejsce tylko na końcu listy i na końcu tablicy.
Dzięki Waszym podpowiedziom forum lazarusa trochę ożyło :) a ja mam od dziś serwer obsługujący bazę danych z możliwością zdalnego odczytania możliwych backupów , ich wykonania zdalnego, oraz wykonywania automatycznych backupu w podanym interwale. (Obsługuje 3 lokalizacje dyskowe). Kolejny krok to program do aktualizacji automatycznej klientów w tle, wykorzystujący funkcje tego serwera).

Bardzo Wam dziękuję , również za pomysł z listą tstringlistów. Nie użyłem ale ciekawa opcja do poćwiczenia jej wykorzystania potem. Najbardziej pomogło przypisanie :=nil

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