Przekazywanie wskaźnika na dynamiczną macierz do rekurencyjnie tworzonych klas tego samego typu

0

Rzadko ostatnio pytam, bo większość potrafię sam rozwiązać, a jak nie to Google chętnie pomaga; Tym razem nie tyle nie wiem jak coś zrobić, ale potrzebuję sprawdzenia czy dobrze to robię;

Otóż kończę już powoli moduł z klasami mojego typu plików konfiguracyjnych i pozostała mi jedynie opcja linkowania plików zewnętrznych podczas parsowania;

Metoda parsująca przetwarza zawartość pliku tekstowego (lub binarnego, ale to nieistotne teraz) i jeśli napotka linię deklaracji pliku zewnętrznego - uruchamia procedurę dodającą na liście przeparsowanych plików nową pozycję, po czym tworzy nową instancję klasy parsera i rozpoczyna parsowanie tego nowego pliku - całość działa rekurencyjnie, bo jeśli plik dołączany także posiada deklarację plików dołączanych, dla nich tworzone są kolejne instancje parsera i tak w koło (rekurencyjnie);

Z racji tej, że pasuje sprawdzać jakie pliki zostały już przeparsowane (aby wykluczyć duplikaty powodujące zapętlenie programu) klasa parsera posiadać musi macierz, w której zapisywane są kolejne parsowane pliki; Przed parsowaniem sprawdzane jest czy nowy plik dołączany istnieje już na liście i jeśli tak - rzucony zostaje wyjątek; Jeśli nie - można parsować;

Problem w tym, że wszystkie instancje klas parserów jakie tworzone są rekurencyjnie muszą korzystać z jednej (tej samej) macierzy z nazwami plików, które już zostały przeparsowane; Wymyśliłem więc, że klasa parsera w konstruktorze otrzymywać będzie wartość logiczną (oznaczać ma to czy utwórzyć w pamięci pola czy przepisać adresy z argumentów do pól klasy), otrzymywać będzie także wskaźnik na macierz z nazwami plików (przy pierwszym utworzeniu będzie to nil) oraz wskaźnik na zmienną z ilością elementów w macierzy (także nil jeśli to pierwsza klasa);

Wszystko w teorii wygląda dobrze, testowałem takie rozwiązanie w odrębnym programie na testowych klasach i wszysko działa jak należy; Obsługa macierzy dynamicznej za pomocą wskaźnika jest nieco utrudniona, ale jakoś się udało; Martwi mnie jednak to, czy napisany kod ma jakieś wycieki lub inne błędy, które mogą crashować program;

Kod programu testowego poniżej:

program datafrnsh;

{$MODE OBJFPC}{$H+}

type
  TLinkedFilesList = array of PAnsiString;
  PLinkedFilesList = ^TLinkedFilesList;

type
  TLinkedFilesInfoRec = record
    FList: PLinkedFilesList;
    FCount: PInteger;
    FDataFurnished: Boolean;
  end;

type
  TContainer = class(TObject)
  private
    FLinkedFilesInfoRec: TLinkedFilesInfoRec;
    FSubContainer: TContainer;
  public
    constructor Create(AFurnishData: Boolean; AList: PLinkedFilesList; ACount: PInteger);
    destructor Destroy(); override;
  public
    procedure AddFileToContainer(AFileName: AnsiString);
    procedure AddFileToSubContainer(AFileName: AnsiString);
    procedure DeleteFile(AIndex: Integer);
  public
    procedure ShowFileNames();
  end;

  constructor TContainer.Create(AFurnishData: Boolean; AList: PLinkedFilesList; ACount: PInteger);
  begin
    inherited Create();

    with FLinkedFilesInfoRec do
    begin
      FDataFurnished := AFurnishData;

      if FDataFurnished then
      begin
        FList := AList;
        FCount := ACount;
        FSubContainer := nil;
      end
      else
      begin
        New(FList);
        SetLength(FList^, 0);
        New(FCount);
        FCount^ := 0;
        FSubContainer := TContainer.Create(True, FList, FCount);
      end;
    end;
  end;

  destructor TContainer.Destroy();
  begin
    with FLinkedFilesInfoRec do
      if FDataFurnished then
      begin
        FList := nil;
        FCount := nil;
        FSubContainer.Free();
      end
      else
      begin
        SetLength(FList^, 0);
        Dispose(FList);
        Dispose(FCount);
      end;

    inherited Destroy();
  end;

  procedure TContainer.AddFileToContainer(AFileName: AnsiString);
  var
    pstrFileName: PAnsiString;
  begin
    with FLinkedFilesInfoRec do
    begin
      SetLength(FList^, FCount^ + 1);
      pstrFileName := @FList^[FCount^];
      pstrFileName^ := AFileName;
      Inc(FCount^);
    end;
  end;

  procedure TContainer.AddFileToSubContainer(AFileName: AnsiString);
  begin
    FSubContainer.AddFileToContainer(AFileName);
  end;

  procedure TContainer.DeleteFile(AIndex: Integer);
  var
    pstrFileName: PAnsiString;
    I: Integer;
  begin
    with FLinkedFilesInfoRec do
      if FCount^ > 0 then
      begin
        pstrFileName := @FList^[AIndex];
        pstrFileName^ := '';

        for I := AIndex + 1 to FCount^ - 1 do
          FList^[I - 1] := FList^[I];

        Dec(FCount^);
        SetLength(FList^, FCount^);
      end;
  end;

  procedure TContainer.ShowFileNames();
  var
    pstrFileName: PAnsiString;
    strFileName: AnsiString;
    I: Integer;
  begin
    with FLinkedFilesInfoRec do
    begin
      WriteLn('Count: ', FCount^, #10);

      for I := 0 to FCount^ - 1 do
      begin
        pstrFileName := @FList^[I];
        strFileName := pstrFileName^;
        WriteLn(strFileName);
      end;

      WriteLn('-----------'#10);
    end;
  end;

begin
  with TContainer.Create(False, nil, nil) do
  try
    AddFileToContainer('+ first');
    AddFileToContainer('+ second');
    AddFileToSubContainer('  + third');
    AddFileToSubContainer('  + fourth');
    AddFileToContainer('+ fifth');
    AddFileToContainer('+ sixth');

    ShowFileNames();
    DeleteFile(0);
    DeleteFile(4);
    DeleteFile(2);
    ShowFileNames();
  finally
    Free();
    ReadLn;
  end;
end.

Wykonanie kodu spowoduje wyświetlenie na ekranie konsoli:

Count: 6

+ first
+ second
  + third
  + fourth
+ fifth
+ sixth
-----------

Count: 3

+ second
  + third
+ fifth
-----------

Pewne zabezpieczenia można jeszcze dodać, ale do testów to co jest wystarczy;

Moje pytanie brzmi: czy powyższy sposób dodawania i usuwania elementów tablicy jest poprawny/optymalny, czy trzeba go jakoś zmienić/zoptymalizować?

Chodzi mi tylko o to samo dodawanie elementów do macierzy i jej czyszczenie - uniknięcie wycieków i innych kłopotów; Podczas działania mechanizmu parsowania elementy nie będą usuwane - po skończeniu parsowania wszystkich plików macierz zostanie wyzerowana i usunięta z pamięci, ale dla testów dodałem i taką metodę, która działa;

Potrzebuję porady, ponieważ jak do tej pory nie operowałem na wskaźniku do dynamicznej macierzy i pomimo tego, że powyższy kod działa - nie mam 100% pewności, że w którymś momencie się nie wywali; Do posta dołączam plik z kodem programu gotowego do skompilowania i przetestowania; Dzięki z góry za pomoc.

2
  1. Czemu nie użyć normalnego TList czy nawet głupiego TStringList?
  2. Po kiego masz zagnieżdżenia, masz przecież wiedzieć czy już przeparsowałeś ten plik czy nie.
  3. Do konstruktora parsera daj dodatkowy ostatni parametr Parsed:TStrings=nil wewnątrz konstruktora:
FFreeList:=(Parsed=nil);
if FFreeList then FParsed:=TStringList.Create else FParsed:=Parsed;
  1. W destruktorze if FFreeList then FParsed.Free;
0
  1. minimalizacja pamięciożerności na rzecz czytelności i wygody :] (a tak naprawdę to i tak chciałem przećwiczyć wskaźniki, więc wykorzystanie takiej macierzy nie było by zły pomysłem)
  2. które zagnieżdżenia?
  3. i 4. w sumie to wiem na jakiej zasadzie rekurencyjnie to parsować/tworzyć instancję klasy/przekazywać wskaźniki, nie wiem tylko czy macierz jest poprawnie czyszczona;

Najmniej roboty będzie przy przekazywaniu listy zamiast macierzy dynamicznej, jednak chciałbym też na przyszłość wiedzieć w jaki sposób obsługiwać macierz dynamiczną na podstawie wskaźnika; No i nie potrzebuję takiego kombajna jak TList, skoro wystarczy przechować tylko i wyłącznie nazwy plików, móc dodawać nowe pozycje, sprawdzić czy plik jest już w macierzy oraz wyczyścić jej zawartość na koniec;

Z tym stosem to ciekawy pomysł - może by zamienić rekurencję na odkładanie na (jakimś) stosie odpowiednich informacji na temat tego co ma być dodatkowo parsowane; Nad tym się jeszcze zastanowię.

1

Zrobiłeś sobie zadanie rodem z Bajtocji i próbujesz rozwiązać go optymalnie. :P
Zastanów się:

  1. Jeżeli plik A dołącza pliki B i C zaś plik B dołącza pliki D i E a plik C dołącza pliki D i F to chcesz dołączać plik D dwa razy? Jeżeli nie, to po powrocie z parsowania B nie należy niczego usuwać.
  2. Ile plików programista "każe" parsować? 10? 100? Bo jak 1000 to zwyczajnie ten programista sknocił projekt, a jak mniej to głupiej klasy TStringList wystarczy jak nic.
0

Zrobiłeś sobie zadanie rodem z Bajtocji i próbujesz rozwiązać go optymalnie. :P

Oj nie przesadzaj, z tym oszczędzaniem pamęci to żart był (dałem buźkę :] )

Jeżeli plik A dołącza pliki B i C zaś plik B dołącza pliki D i E a plik C dołącza pliki D i F to chcesz dołączać plik D dwa razy? Jeżeli nie, to po powrocie z parsowania B nie należy niczego usuwać.

Nie wiem czy się rozumiemy, ale każdy plik może być tylko raz parsowany - dlatego potrzebuję mieć jedną listę, która przed parsowaniem będzie sprawdzana w poszukiwaniu duplikatów; Jeśli w różnych plikach (lub nawet w tym samym) będzie odwołanie do tego samego pliku to jest to błąd (nie projektowy, a programisty albo użytkownika, który tak utworzył/zmodyfikował pliki); Dodatkowo to będzie zapezpieczenie przed samozapętleniem, jeśli wystąpi circular reference; Według mnie musi być takie zabezpieczenie, żeby użytkownik przez zmodyfikowanie plików (które są de facto tekstowe) nie mógł zepsuć całego programu;

Ile plików programista "każe" parsować? 10? 100? Bo jak 1000 to zwyczajnie ten programista sknocił projekt, a jak mniej to głupiej klasy TStringList wystarczy jak nic.

Tego ile plików będzie połączonych ze sobą odwołaniami także nie można przewidzieć - zależy od programisty ile będzie chciał łączyć; Z takiej macierzy jak w przykładzie pierwotnie chciałem skorzystać, ale nie do końca wiedziałem co z usuwaniem łańcuchów i całej macierzy; Teraz widzę, że raczej ten pomysł odpada, bo po co marnować czas i nerwy na zabawę we wskaźniki (z których i tak korzystam sporadycznie, w tym module jedynie przy wypakowywaniu "składników" linii) - łatwiej przekazywać listę, bo to w sumie i tak wskaźnik jest;

Ale dzięki za sugestie - choć dalej nie wiem czy poprawnie są usuwane elementy macierzy :P

1

Z Bajtocją - miałem na myśli samo zadanie a nie sposób realizacji.

Nie musimy się rozumieć teksem, rozumiemy się kodem :D
Skoro w destruktorze niszczysz listę to oznacza że po zakończeniu parsowania pliku B (z poprzedniego mojego przykładu) usuniesz z listy informacje o parsowaniu plików D i E więc z poziomu C znowu będziesz parsować D.

Teoretycznie wszystko poprawnie, ale:

  1. Po kiego ci ACount:PInteger przecież Length(AList^.Flist^) zwróci ci rozmiar.
  2. Skoro pozbywamy się ACount:PInteger to możemy pozbyć się AFurnishData:Boolean; określając to następująco: FDataFurnished:=(AList<>nil).
  3. W ten sposób możemy się pozbyć struktury TLinkedFilesInfoRec.
    No i na koniec zastąp array of PAnsiString na TStrings z implementacją przez TStringList
0

Skoro w destruktorze niszczysz listę to oznacza że po zakończeniu parsowania pliku B (z poprzedniego mojego przykładu) usuniesz z listy informacje o parsowaniu plików D i E więc z poziomu C znowu będziesz parsować D.

W tym rzecz, że właśnie nie usunę, bo podklasy w destruktorze nilują wskaźnik, a nie usuwają go z pamięci:

destructor TContainer.Destroy();
begin
  with FLinkedFilesInfoRec do
    if FDataFurnished then
    begin
      FList := nil;
      FCount := nil;
      FSubContainer.Free();
    end
    else
    begin
      SetLength(FList^, 0);
      Dispose(FList);
      Dispose(FCount);
    end;
 
  inherited Destroy();
end;

Pierwsza instacja klasy (która może tworzyć następne) będzie w konstruktorze tworzyć (za pomocą New) oba wskaźniki, a kolejne rekurencyjnie tworzone instancje tylko przepiszą adresy z argumentów konstruktora; Tak więc każda klasa parsera po skończeniu pracy sprawdza w destruktorze czy sama stworzyła te wskaźniki (czyli pole FDataFurnished struktury), czy dostała adresy do przepisania; Jeśli sama je stworzyła - usuwa je (za pomocą Dispose), a jeśli nie to niluje zmienne (choć to i tak nie jest obowiązkowe); Poszczególne elementy macierzy nie są usuwane przez kolejne klasy po skończeniu parsowania, tak że dopóki pierwsza instancja nie zakończy działania każda kolejna będzie dorzucać nowe elementy do tablicy;

Przykładowy schemat połączeń:

plik A
  link do pliku B
  link do pliku C

plik B
  link do pliku D
  link do pliku E

plik C
  link do pliku F
  link do pliku G

Kolejność parsowania:

plik A
  plik B
    plik D
    plik E
  plik C
    plik F
    plik G

i dokładnie w takiej kolejności będą pliki w macierzy po skończeniu parsowania, gdzie pierwsza utworzona klasa parsera na koniec tylko i wyłącznie czyści macierz i usuwa ją z pamięci; Więc jeśli zmodyfikujemy połączenia tak, by odwoływały się co najmniej dwa razy do tego samego pliku:

plik A
  link do pliku B
  link do pliku C

plik B
  link do pliku D
  link do pliku E

plik C
  link do pliku F
  link do pliku B <- tutaj dubel

to podczas rekurencyjnego parsowania po raz drugi plik B zostałby przeparsowany, co jest błędem; A z racji tej, że żadna z klas podrzędnych nie będzie usuwać przeparsowanych plików z macierzy - wpis pliku B będzie widoczny aż do przeparsowania wszystkich plików przez wszystkie klasy (dlatego to przekazywanie wskaźnika do kolejnych klas);

Funkcję usuwanie elementów z tablicy niepotrzebnie dodałem do tego testowego programu, bo teraz widzę, że wprowadza w błąd; Ale cały czas chodzi mi o to, że tylko pierwsza utworzona klasa parsera ma "moc" czyszczenia macierzy, klasy podrzędne tylko dorzucają nowe pliki na kupkę (pod warunkiem, że na tej kupce nie istnieje ten, którego chcą parsować);

Myślę, że teraz wszystko już jest jasne :]


Wiem, że ten program testowy nie jest najlepszy, ale napisany na szybko po to, żeby uwidocznić sam system przekazywania wskaźników; No i chyba bardziej zagmatwał sprawę, niż pomógł;

Pole FCount jest przydatne, bo nie muszę obliczać indeksu dla nowego elementu przed jego dodaniem, no i nieco upraszcza zapis; Z tym Length(AList^.Flist^) pomyliłeś, bo AList to argument konstruktora, a FList to pole struktury, ale mimo wszystko wiem co masz na myśli.

0

Spójrz to niżej robi dokładnie to samo co twoja klasa i zauważ że nawet nie trzeba nikogo pytać czy dobrze działa, bo jest to ewidentnie.

type
  TContainer = class(TObject)
  private
    FList:TStrings;
    FOwnList:Boolean;
  public
    constructor Create(AFList:TStrings=nil);
    destructor Destroy();override;
  public
    procedure AddFileToContainer(AFileName:AnsiString);
    procedure DeleteFile(AIndex:Integer);
  public
    procedure ShowFileNames();
  end;
 
  constructor TContainer.Create(AFList:TStrings);
  begin
    inherited Create();
    FOwnList:=(AFList=nil);
    if FOwnList then FList:=TStringList.Create else FList:=AList;
  end;
 
  destructor TContainer.Destroy();
  begin
    if FOwnList then FList.Free;
    inherited Destroy();
  end;
 
  procedure TContainer.AddFileToContainer(AFileName:AnsiString);
  begin
    FList.Add(AFileName);
  end;
  
  procedure TContainer.DeleteFile(AIndex:Integer);
  begin
    FList.Delete(AIndex);
  end;
 
  procedure TContainer.ShowFileNames();
  var I:Integer;
  begin
    WriteLn('Count: ', FList.Count, #10);
    for I := 0 to FList.Count - 1 do WriteLn(FList[I]);
    WriteLn('-----------'#10);
  end;

begin
  with TContainer.Create() do
  try
    AddFileToContainer('+ first');
    AddFileToContainer('+ second');
    with TContainer.Create(Flist) do
    try
      AddFileToContainer(' + third');
      AddFileToContainer(' + fourth');
    finally
      Free;
    end;
    AddFileToContainer('+ fifth');
    AddFileToContainer('+ sixth');
 
    ShowFileNames();
    DeleteFile(0);
    DeleteFile(4);
    DeleteFile(2);
    ShowFileNames();
  finally
    Free;
    ReadLn;
  end;
end.
0

Spójrz to niżej robi dokładnie to samo co twoja klasa i zauważ że nawet nie trzeba nikogo pytać czy dobrze działa, bo jest to ewidentnie.

Eh, widzę, że dalej się nie rozumiemy...

@_13th_Dragon - ja nie założyłem tego wątku po to, by mi ktoś wytłumaczył jak przekazywać wskaźnik (bez względu na to czy to wskaźnik na macierz czy listę), tylko żeby ktoś sprawdził lub podpowiedział jak poprawnie usuwać elementy/czyścić dynamiczną macierz na podstawie wskaźnika; Ja wiem jak mój kod testowy (i właściwą klasę parsera) przystosować pod listy; Twój kod na szybko przeanalizowany wygląda na poprawny i powinien działać;

Pytanie jest jednak inne - jak poprawnie dodawać i usuwać elementy do/z macierzy dynamicznej AnsiStringów mając do niej wskaźnik? To chciałbym się dowiedzieć, a to czy wykorzystam macierz czy listę to inna sprawa (a właściwie to dzięki Twoim namowom wykorzystam listę, choć chciałem tego uniknąć);

Więc podsumowując - czy metoda TContainer.DeleteFile (z pierwszego posta) jest poprawna, czy czegoś brakuje lub coś jest robione niepotrzebnie? :]

2

Ok, to trzeba było tak wprost pisać zamiast wymyślać bajeczkę z Bajtocji. Zrobione jest poprawnie ale pomysł jest poroniony, czemu? najprościej to zademonstrować na przykładzie: http://ideone.com/i4NPyA

0

Ok, to trzeba było tak wprost pisać zamiast wymyślać bajeczkę z Bajtocji.

Może i nadłożyłem nieco informacji, ale wolałem napisać od razu w czym to chcę wykorzystywać żeby później ktoś nie musiał się dopytywać; Ponadto pytanie jest pogrubione, żeby nie było problemu z jego znalezieniem;

Zrobione jest poprawnie

I o co cały czas chodzi, nareszcie odpowiedź na pytanie :]

najprościej to zademonstrować na przykładzie

Ale ten przykład jest niepoprawny, bo łańcuch jest tworzony w lokalnej funkcji, a po jej zakończeniu łańcuch jest usuwany z pamięci, więc wskaźnik wskazuje na śmieci - w ten sposób refcount nie jest zwiększany; Czegoś takiego by nie było, bo po to właśnie przekazywany jest wskaźnik na całą macierz żeby uniknąć takich błędów (od których kod z pierwszego posta jest uwolniony);

To tyle co chciałem wiedzieć; Skoro usuwanie/czyszczenie jest dobrze zrobione, to oznaczam Twój post jako rozwiązanie; Sugestia z wykorzystaniem listy oczywiście będzie wykorzystana, ale miło wiedzieć jak obsługiwać taką dynamiczną macierz; Dzięki za pomoc.

1
furious programming napisał(a):

Ale ten przykład jest niepoprawny, bo łańcuch jest tworzony w lokalnej funkcji, a po jej zakończeniu łańcuch jest usuwany z pamięci, więc wskaźnik wskazuje na śmieci - w ten sposób refcount nie jest zwiększany; Czegoś takiego by nie było, bo po to właśnie przekazywany jest wskaźnik na całą macierz żeby uniknąć takich błędów (od których kod z pierwszego posta jest uwolniony);

Dobrze że zauważyłeś problem w moim przykładzie. Teraz zobacz ten sam problem tu:

  procedure TContainer.AddFileToContainer(AFileName: AnsiString);
  var
    pstrFileName: PAnsiString;
  begin
    with FLinkedFilesInfoRec do
    begin
      SetLength(FList^, FCount^ + 1);
      pstrFileName := @FList^[FCount^];
      pstrFileName^ := AFileName;
      Inc(FCount^);
    end;
  end;

Uratowało cię to że w kodzie:

    AddFileToContainer('+ first');
    AddFileToContainer('+ second');
    AddFileToSubContainer('  + third');
    AddFileToSubContainer('  + fourth');
    AddFileToContainer('+ fifth');
    AddFileToContainer('+ sixth');

Napisy tworzone w jednym miejscu.

Poza tym dopiero zauważyłem :D

      SetLength(FList^, FCount^ + 1); // rozszerzyłeś tablicę dynamiczną, na końcu masz dopisany adres AnsiString'a wyzerowany
      pstrFileName := @FList^[FCount^]; // pobrałeś adres AnsiString'a który jest wyzerowany i od tego pobierasz adres? WTF?

Teoretycznie robisz PAnsiString=PPAnsiString; nie mam zielonego pojęcia jak to działa.
Wg mnie to jakieś dziwne mazanie po pamięci, kiedyś się zemści.
Jeżeli już to zrób tablicę TLinkedFilesList = array of AnsiString; lub TLinkedFilesList = array of PChar;

0

Uratowało cię to że w kodzie: [...] Napisy tworzone w jednym miejscu.

Nie ma znaczenia gdzie są dodawane pliki do macierzy, bo i tak tą kwestią zajmuje się obiekt, a dokładnie jego metoda; Bez względu na to gdzie w programie są kolejne instancje klas (czy to w jednej procedurze, czy w kilku, czy nawet w kilku modułach), bo wszystkie i tak posiadają jeden i ten sam wskaźnik na tylko jedną macierz; Wszystko działa w obrębie jednego programu, ale gdyby program miał kilka modułów i w każdym module tworzona była kolejna instancja klas, to i tak przekazany byłby jeden wskaźnik, czyli wszystkie klasy modyfikowały by tą samą tablicę;

Wiem, że to zagmatwane jest, ale skuteczne i wygodne (pomijając zabawę ze wskaźnikami, bo chodzi mi o przekazywanie jednej listy plików do wielu klas nie udostępniając jej na zewnątrz klasy parsera) to samo będzie z listą, bo klasa główna tworzy listę, a kolejne dostają do niej referencję i tym samym wszystkie działają na jednej liście;

SetLength(FList^, FCount^ + 1); // rozszerzyłeś tablicę dynamiczną, na końcu masz dopisany adres AnsiString'a wyzerowany

W tym rzecz, że nie - rozszerzając macierz widać automatycznie alokowana jest pamięć dla nowego łańcucha (z pustą wartością), więc pobierając do niego adres:

pstrFileName := @FList^[FCount^]; // pobrałeś adres AnsiString'a który jest wyzerowany i od tego pobierasz adres? WTF?

dostaję adres na zaalokowany łańcuch, ale z pustą wartością (pusty string); Gdyby było inaczej to wyrzucony byłby wyjątek naruszenia pamięci SIGSEGV (w przypadku Delphi AccessViolation) podczas próby wpisania danych pod ten adres;

Teoretycznie robisz PAnsiString=PPAnsiString; nie mam zielonego pojęcia jak to działa.

To jest jedyna wersja, która nie dość, że jest poprawna składniowo, to jeszcze działa bez zarzutu;

Ten kod:

pstrFileName := @FList^[FCount^];

to nic innego jak pobranie wskaźnika na ostatni element macierzy i jest poprawny składniowo, bo:

  • FList jest wskaźnikiem na macierz, więc aby dobrać się do jego wartości trzeba napisać FList^ (z daszkiem),
  • z tej operacji ma być pobrany adres, więc aby to zrobić trzeba napisać @FList^ (z małpką),
  • żeby pobrać adres ostatniego elementu tablicy, trzeba napisać @FList^[], a jako indeks ostatniego elementu trzeba podać wartość wskaźnika FCount, czyli FCount^, co sprowadza się do zapisu @FList^[FCount^];
    Dlatego, że wskaźniki i operacje na nich są tak rąbnięte w zapisie - rzadko z nich korzystam; Tym bardziej tutaj, gdzie wskaźnik ma wskazywać na dynamiczną macierz, ale w sumie ten zapis jest logiczny i poprawny, dziwi tylko sam fakt alokacji pamięci dla nowego elementu w macierzy - to akurat robione jest widać automagicznie;

Wg mnie to jakieś dziwne mazanie po pamięci, kiedyś się zemści.

Sprawdzałem to na różne sposoby i jedyne co poprawnie działa (nawet pod debugerem) to właśnie ten kod z pierwszego posta; Żadnych wyjątków czy mazania po niezaalokowanej pamięci; Bo gdyby tak było - wyrzucane były by wyjątki albo w oknie konsoli wyświetlały by się krzaki (i tak się kilka razy działo, jak próbowałem różnych sposobów); Ten w pierwszym poście nie powoduje żadnych błędów - sprawdzałem także pod Delphi7 i działa poprawnie (bez wyjątków, bez krzaków na ekranie itd.);

Chciałem ten sposób wykorzystać, bo byłby dużo szybszy od listy, ale z racji tej, że mogą wystąpić problemy (choć na razie ich nie widzę) wolę wykorzystać listę; No i taka umiejętność przyda się może kiedyś, tak że nie żałuję, że spróbowałem i w ten sposób :]


EDIT: Sprawdziłem jeszcze raz pod debugerem i potwierdza się fakt, że po zwiększeniu tablicy alokowana jest pamięć automatycznie, więc ta linia:

pstrFileName := @FList^[FCount^];

wpisze poprawny adres do zmiennej pstrFileName, więc i dostęp do łańcucha jest jak najbardziej możliwy i żadnych błędów nie powinno być (i póki co nie ma); I Lazarus, i Delphi7 radzą sobie bez problemu, ale wolę tego nie wykorzystywać, żeby później tego nie poprawiać, jeśli istnieje szansa, że coś się wykrzaczy :D

2

Zastanów się jeszcze raz:

FList^[FCount^] - zwraca ci już PAnsiString

http://ideone.com/YeBUxM

0

http://ideone.com/NQGRYy

Hmm... Dziwne to, teoretycznie w tym kodzie co podałem w poprzednim poście powinno wywalić SIGSEGV, bo adres wskaźnika (tak jak napisałeś wcześniej - PPAnsiString) to nie to samo co wskaźnik na wartość (czyli PAnsiString), ale w ten sposób działa i to jest dziwne; W ten sposób powinienem zamiast wskaźnika na wartość dostać adres samego wskaźnika w pamięci, co po przypisaniu wartości powinno się wywalić;

Twój przykład nie do końca odzwierciedla to co trzeba zrobić, bo operujesz na Arr (czyli tablicy PAnsiString), a ja przekazuję wskaźnik do takiej tablicy, czyli PArr i na nim operuję; Dodatkowo nie mogę elementom w tablicy przekazać adresu na ciąg - muszę go tam wpisać (czyli skopiować wartość), żeby po wyjściu z procedur parsujących nie stracić wartości (po wyjściu z procedury ciąg byłby usuwany, więc wskaźnik wskazywałby na krzaki); Dlatego też w moim programie testowym dodawanie do tablicy nowego ciągu jest w metodzie, po skończeniu której łańcuch jest kasowany, a pomimo tego w tablicy nadal pozostaje nieutracona wartość;

Muszę to dokładniej sprawdzić, ale to w ramach ciekawostki, bo wykorzystanie takiego dziwoląga odpadło na rzecz listy;


Według tego co piszesz, to w moim kodzie zamiast tej linijki:

pstrFileName := @FList^[FCount^];

powinno być:

pstrFileName := FList^[FCount^];

Ale pomimo tego, że kod się kompiluje wyrzyca SIGSEGV w linii:

pstrFileName^ := AFileName;

czyli musiał pobrać zły adres; Jeśli jednak wykonam to po swojemu to działa; Niestety bezpośrednio do macierzy wpisać nie mogę, bo SIGSEGV:

// obie wersje kompilują się, ale wyrzucają błąd:
FList^[FCount^]^ := AFileName;
(FList^[FCount^])^ := AFileName;

stąd też muszę wykorzystać dodatkową zmienną, czyli pstrFileName i najpierw kazać jej wskazywać na element w macierzy, a następnie wpisać tam dane:

pstrFileName := @FList^[FCount^];
pstrFileName^ := AFileName;

gdzie błędu już nie ma; Opcja z wykorzystaniem funkcji Addr także działa poprawnie:

pstrFileName := Addr(FList^[FCount^]);
pstrFileName^ := AFileName;

Sam się dziwię dlaczego tak jest, ale to jedyny w moim przypadku działający poprawnie kod.

0

A ja już rozumiem bo de facto używasz array of AnsiString tylko że w strasznie zagmatwany sposób.

A żebyś wiedział - nie wiem po jaką cholerę mi tablica PAnsiString (to utrudnia), skoro może być tablicą AnsiString;

Może nie zauważyłeś ale dalej jest P:=@Arr i to P jest odpowiednikiem tego twego FList

Z tym się nie zgodzę, bo Ty pobierasz adres na macierz już istniejącą, a ja dynamicznie alokuję pamięć dla takiej tablicy mając tylko i wyłącznie wskaźnik; Wszystkie Twoje zmiany na P odbywają się de facto na zmiennej Arr, a u mnie nie ma zmiennej, na którą wskazuje FList - on sam jest dynamicznie zaalokowany przez New;

W sumie to jeden pieron, bo FList mógłby być zwykłym FList: array of AnsiString;, a do kolejnych klas tylko podawać adres do FList, ale rekurencyjny sposób odpada, bo zmienna tablicowa nie mogłaby być polem klasy.

0

Jaka różnica jak go alokujesz, czyli jak uzyskujesz wskaźnik na istniejąca tablice dynamiczną, przecież dyskutujemy o jej wypełnieniu?
Nie jest to offtop ponieważ nie możemy rozmawiać o poprawnym zwolnieniu niepoprawnie zadeklarowanej/wypełnionej listy/macierzy/itp.
W każdym razie przekombinowałeś ...

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