Dynamiczne alokacja pamięci

0

Cześć, mam taki record z innymi danymi typu class w składzie :

PDaneZamowienia=^TDaneZamowienia;
  TDaneZamowienia=record
  NrZamowienia:Integer;
  Status:String;
  InfoWydruk:String;
  DataZamowienia:TDate;
  
  PWykonawca:^TDaneFirmy;
  PZamawiajacy:^TKontrahenci;
       

Odwołuję się do tej struktury tak, w zaznaczonym miejscu powstaje problem z dostępem:

 PDaneZamowienia:^TDaneZamowienia; 
 
 New(PDaneZamowienia);
 New(PDaneZamowienia^.PWykonawca);
 New(PDaneZamowienia^.PZamawiajacy); 

 PDaneZamowienia^.NrZamowienia:='11111'

 PDaneZamowienia^.PWykonawca^.Nazwa:='aaaaaa'; // -->> TUTAJ POWSTAJE PROBLEM Access violation.
 ShowMessage(PDaneZamowienia^.PWykonawca^.Nazwa); // -->> LUB TUTAJ POWSTAJE PROBLEM Access violation.

 PDaneZamowienia^.PZamawiajacy^.Nazwa:='bbbbbbbb'; // -->> TUTAJ POWSTAJE PROBLEM Access violation.
 ShowMessage(PDaneZamowienia^.PZamawiajacy^.Nazwa);  // -->> LUB TUTAJ POWSTAJE PROBLEM Access violation.
                                                 

Dodam, że błędu nie wywala za każdym razem, czasami te linie przechodzą o problem pojawia na dalszych składowych rekordu np. PDaneZamowienia^.PZamawiajacy^.NIP

Poniżej jeszcze struktury TDaneFirmy i TKontrahenci - tutaj są to struktury class;

TKontrahenci=class
public
 IloscBaza:Integer;
 Id:Integer;
 Identyfikator:AnsiString;
 Nazwa:AnsiString;
 Ulica:AnsiString;
 KodPocztowy:AnsiString;
 Miasto:AnsiString;
 NIP:AnsiString;
 Telefon:AnsiString;
 Email:AnsiString;
 TerminPlatnosci:Integer;
 Samofakturowanie:Integer;
end;                  

TDaneFirmy=class
public
 Id:Integer;
 Nazwa:AnsiString;
 KodPocztowy:AnsiString;
 Miasto:AnsiString;
 Ulica:AnsiString;
 Telefon:AnsiString;
 Fax:AnsiString;
 Email:AnsiString;
 HTTP:AnsiString;
 NIP:AnsiString;
 Bank:AnsiString;
 Konto:AnsiString;
 BDO:AnsiString;
end;                  

2

Dlaczego instancje klas tworzysz przez New? (tu jest powód błędu, ale to chyba mniejszy problem w tym kodzie :P)
W ogóle dlaczego "TKontrahenci" i "TDaneFirmy" to klasy? W tym co masz powyżej nie ma to zupełnie sensu. To kompletny kawałek kodu czy powycinałeś większość deklaracji?

Poza tym, jak już przerobisz oba na rekordy, możesz je zwyczajnie umieścić w TDaneZamowienia bez żadnych wskaźników.
W ogóle po co używasz tu wskaźników do rekordów? Nie wiem oczywiście jaką masz sytuacje ale przy tym co pokazałeś to nie ma to zupełnie żadnego sensu.

btw.
IMHO tragedią jest sytuacja gdy alokujesz pamięć na nowy rekord a potem, dalej w kodzie, tworzysz obiekty w tym rekordzie czy inaczej alokujesz pamięć. Takie rzeczy powinny być wyrzucone do konstruktora (i destruktora) tak żeby reszta programu nie wiedziała kto i skąd ma tą pamięć. To też by załatwiło kwestię zwolnienia później tej pamięci. Choć jeśli to co napisałeś to kompletne deklaracje, to IMHO w tym wypadku można użyć samych rekordów nie wchodząc we wskaźniki ani klasy.

0

zobacz sobie pod debuggerem co tam masz w tych zmiennych

0
dziobu napisał(a):

Dlaczego instancje klas tworzysz przez New? (tu jest powód błędu, ale to chyba mniejszy problem w tym kodzie :P)
W ogóle dlaczego "TKontrahenci" i "TDaneFirmy" to klasy? W tym co masz powyżej nie ma to zupełnie sensu. To kompletny kawałek kodu czy powycinałeś większość deklaracji?

Poza tym, jak już przerobisz oba na rekordy, możesz je zwyczajnie umieścić w TDaneZamowienia bez żadnych wskaźników.
W ogóle po co używasz tu wskaźników do rekordów? Nie wiem oczywiście jaką masz sytuacje ale przy tym co pokazałeś to nie ma to zupełnie żadnego sensu.

btw.
IMHO tragedią jest sytuacja gdy alokujesz pamięć na nowy rekord a potem, dalej w kodzie, tworzysz obiekty w tym rekordzie czy inaczej alokujesz pamięć. Takie rzeczy powinny być wyrzucone do konstruktora (i destruktora) tak żeby reszta programu nie wiedziała kto i skąd ma tą pamięć. To też by załatwiło kwestię zwolnienia później tej pamięci. Choć jeśli to co napisałeś to kompletne deklaracje, to IMHO w tym wypadku można użyć samych rekordów nie wchodząc we wskaźniki ani klasy.

W tych akurat class-ach nie ma akurat żadnych funkcji, procedur ani nawet konstruktora - masz rację powinny to być raczej dane typu record ale mam naleciałości z C++ :( Te obiekty klasowe wykorzystuję tylko do tworzenia tablic (SetLength(KontrahenciRaport,SumaKontrahentow); KontrahenciRaport[I]:=TKontrahenci.Create; KontrahenciRaport[I].Free;). Czyli podsumowując albo zmiana class na record albo dopisanie konstruktora i destruktora ?

1

Konstruktor i destruktor załatwią ci tworzenie i zwalnianie podrzędnych.
Ręczne New wymaga ręcznego Dispose, można wszystko robić ręcznie ale nie dla tego istnieje programowanie obiektowe.

1

"Te obiekty klasowe wykorzystuję tylko do tworzenia tablic "

zamiast tablic użyj kontenera dedykowanego do przechowywania listy obiektów czyli generycznej listy Tobjectlist

unit Unit3;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, generics.collections, StdCtrls;

type
  TForm3 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

  TKontrahenci = class
  public
    IloscBaza: Integer;
    Id: Integer;
    Identyfikator: AnsiString;
    Nazwa: AnsiString;
    Ulica: AnsiString;
    KodPocztowy: AnsiString;
    Miasto: AnsiString;
    NIP: AnsiString;
    Telefon: AnsiString;
    Email: AnsiString;
    TerminPlatnosci: Integer;
    Samofakturowanie: Integer;
  end;

  TDaneFirmy = class
  public
    Id: Integer;
    Nazwa: AnsiString;
    KodPocztowy: AnsiString;
    Miasto: AnsiString;
    Ulica: AnsiString;
    Telefon: AnsiString;
    Fax: AnsiString;
    Email: AnsiString;
    HTTP: AnsiString;
    NIP: AnsiString;
    Bank: AnsiString;
    Konto: AnsiString;
    BDO: AnsiString;
  end;

  TDaneZamowienia = class
    NrZamowienia: Integer;
    Status: String;
    InfoWydruk: String;
    DataZamowienia: TDate;
    Wykonawca: TDaneFirmy;
    Zamawiajacy: TKontrahenci;
    constructor Create;
    destructor Destroy; override;
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

procedure TForm3.Button1Click(Sender: TObject);
var
  listaZamowien: TobjectList<TDaneZamowienia>;
  lObject: TDaneZamowienia;
  I: Integer;
begin
  listaZamowien := TobjectList<TDaneZamowienia>.Create(true);
  try
    for I := 1 to 100 do
    begin
      lObject := TDaneZamowienia.Create;
      lObject.NrZamowienia := I;
      listaZamowien.add(lObject);
    end;
    /// np. tutaj  jakieś działania na obiektach listy 'listaZamowien'  
    ///
    ///
    
  finally
    listaZamowien.free;
  end;

end;

{ TDaneZamowienia }

constructor TDaneZamowienia.Create;
begin
  self.Wykonawca := TDaneFirmy.Create;
  self.Zamawiajacy := TKontrahenci.Create;
end;

destructor TDaneZamowienia.Destroy;
begin
  inherited;
  self.Wykonawca.free;
  self.Zamawiajacy.free;
end;

end.
``
4

Po pierwsze, w kodzie jest bajzel — nazwy są po polsku, przedrostki są używane niepoprawnie i w niektórych miejscach brakuje typów danych. Poza tym mieszasz koncepcję struktur z klasami, co w Pascalach nie ma za bardzo sensu. No i formatowanie kuleje, co utrudnia czytanie kodu.

Ogólnie to nie ma żadnego problemu z pisaniem kodu proceduralnie oraz z wykorzystaniem struktur danych oraz wskaźników, zamiast instancji klas. Wiem o tym, bo obecnie piszę dużą grę właśnie w ten sposób — rekordy, wskaźniki i zestawy funkcji do ich obsługi. Tyle że nie bawię się w New i Dispose, a używam GetMem, AllocMem, ReallocMem i FreeMem, bo dają większe możliwości (alokuję tyle bajtów ile chcę, a nie tyle ile kompilator obliczy na podstawie typu danych).

Jeśli koniecznie potrzebujesz pisać bez użycia obiektów, to:

  • nazwij sensownie typy danych — użyj przedrostków T i P dla wszystkich potrzebnych typów,
  • jeśli gdzieś używasz wskaźników na własne typy danych, to zadeklaruj je jako osobne typy (deklaruj parami, np. TDaneZamowienia i PDaneZamowienia),
  • nie używaj przedrostków T i P w identyfikatorach zmiennych, pól struktur itd.,
  • w każdej strukturze umieść pola w sekcji private, aby nie były widoczne poza modułem ich deklaracji, czyli aby nie było do nich bezpośredniego dostępu (masz hermetyzację i bezpieczeństwo za jednym zamachem, dodatkowa kontrola nie jest potrzebna),
  • do obsługi danej struktury przygotuj zestaw funkcji, w tym funkcję tworzącą strukturę oraz ją niszczącą, a więc alokującą pamięć dla struktury i jej składowych oraz dealokującą wszystko,
  • w Delphi nie musisz korzystać z operatora ^ podczas dereferencji (to tylko hint — jeśli chcesz to z niego korzystaj),
  • nie operuj na całych strukturach, a używaj do nich pointerów — funkcja tworząca niech zwraca wskaźnik, funkcje do odczytu i modyfikacji danych niech pobierają wskaźnik, funkcja niszcząca też niech pobiera wskaźnik,
  • jeśli robisz funkcje zwracające jakieś pojedyncze dane ze struktury (a la gettery), to oznaczaj je jako inline — to da wydajność identyczną jak w przypadku dostępu bezpośredniego.

Jeśli natomiast nie musisz pisać nieobiektowo (a raczej bez klas), to odradzam pisania w ten sposób, bo to znacznie trudniejsze niż ”klasowe” OOP. Jeśli nie czujesz się pewnie w temacie wskaźników, to odpuść, bo ciągle będziesz dostawał wyjątki AV i wycieki pamięci. Do wskaźników naprawdę trzeba mieć głowę, tak samo jak do wątków.

Natomiast Twój problem polega na tym, że:

PDaneZamowienia^.PWykonawca^.Nazwa:='aaaaaa'; // -->> TUTAJ POWSTAJE PROBLEM Access violation.
ShowMessage(PDaneZamowienia^.PWykonawca^.Nazwa); // -->> LUB TUTAJ POWSTAJE PROBLEM Access violation.

PDaneZamowienia^.PZamawiajacy^.Nazwa:='bbbbbbbb'; // -->> TUTAJ POWSTAJE PROBLEM Access violation.
ShowMessage(PDaneZamowienia^.PZamawiajacy^.Nazwa);  // -->> LUB TUTAJ POWSTAJE PROBLEM Access violation.

PDaneZamowienia^.PWykonawca to wskaźnik na instancję klasy, a więc wskaźnik na wskaźnik, więc aby dobrać się do składowych PWykonawca, potrzebujesz czegoś na kształt podwójnej dereferencji. Problem jednak w tym, że to nie ma sensu, bo nie po to referencja jest wskaźnikiem, aby dobierać się do niej poprzez jeszcze inny wskaźnik. Zmień typ TDaneFirmy z klasy na rekord i powinno działać.

0
grzegorz_so napisał(a):

"Te obiekty klasowe wykorzystuję tylko do tworzenia tablic "

zamiast tablic użyj kontenera dedykowanego do przechowywania listy obiektów czyli generycznej listy Tobjectlist

unit Unit3;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, generics.collections, StdCtrls;

type
  TForm3 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

  TKontrahenci = class
  public
    IloscBaza: Integer;
    Id: Integer;
    Identyfikator: AnsiString;
    Nazwa: AnsiString;
    Ulica: AnsiString;
    KodPocztowy: AnsiString;
    Miasto: AnsiString;
    NIP: AnsiString;
    Telefon: AnsiString;
    Email: AnsiString;
    TerminPlatnosci: Integer;
    Samofakturowanie: Integer;
  end;

  TDaneFirmy = class
  public
    Id: Integer;
    Nazwa: AnsiString;
    KodPocztowy: AnsiString;
    Miasto: AnsiString;
    Ulica: AnsiString;
    Telefon: AnsiString;
    Fax: AnsiString;
    Email: AnsiString;
    HTTP: AnsiString;
    NIP: AnsiString;
    Bank: AnsiString;
    Konto: AnsiString;
    BDO: AnsiString;
  end;

  TDaneZamowienia = class
    NrZamowienia: Integer;
    Status: String;
    InfoWydruk: String;
    DataZamowienia: TDate;
    Wykonawca: TDaneFirmy;
    Zamawiajacy: TKontrahenci;
    constructor Create;
    destructor Destroy; override;
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

procedure TForm3.Button1Click(Sender: TObject);
var
  listaZamowien: TobjectList<TDaneZamowienia>;
  lObject: TDaneZamowienia;
  I: Integer;
begin
  listaZamowien := TobjectList<TDaneZamowienia>.Create(true);
  try
    for I := 1 to 100 do
    begin
      lObject := TDaneZamowienia.Create;
      lObject.NrZamowienia := I;
      listaZamowien.add(lObject);
    end;
    /// np. tutaj  jakieś działania na obiektach listy 'listaZamowien'  
    ///
    ///
    
  finally
    listaZamowien.free;
  end;

end;

{ TDaneZamowienia }

constructor TDaneZamowienia.Create;
begin
  self.Wykonawca := TDaneFirmy.Create;
  self.Zamawiajacy := TKontrahenci.Create;
end;

destructor TDaneZamowienia.Destroy;
begin
  inherited;
  self.Wykonawca.free;
  self.Zamawiajacy.free;
end;

end.
``

Dzięki wszystkim za uwagi. Proszę jeszcze o informację jak właściwie stworzyć w konstruktorze dynamiczną tablicę zadeklarowaną clasie TDaneZamowienia:

 TDaneZamowienia = class
>     NrZamowienia: Integer;
>     Status: String;
>     InfoWydruk: String;
>     DataZamowienia: TDate;
>     Wykonawca: TDaneFirmy;
>     Zamawiajacy: array of TKontrahenci; //<<--- co w construktor i destructor dla tej deklaracji
>     constructor Create;
>     destructor Destroy; override;
>   end;

oraz jak ją prawidłowo zlikwidować w destruktorze ?

0

Zamiast tablicy radził bym użyć kontenera dedykowanego do przechowywania listy obiektów jakim jest klasa Tobjectlist. Masz to pokazane w przykładzie

type
  TDaneZamowienia = class
  public
    NrZamowienia: Integer;
    Status: String;
    InfoWydruk: String;
    DataZamowienia: TDate;
    Wykonawca: TDaneFirmy;
    Zamawiajacy: TobjectList<TKontrahenci>;
    procedure jakasmetoda;
    constructor Create;
    destructor Destroy; override;
  end;


constructor TDaneZamowienia.Create;
begin
  Zamawiajacy := TobjectList<TKontrahenci>.Create(true);
  Wykonawca := TDaneFirmy.Create;
end;

destructor TDaneZamowienia.Destroy;
begin
  Wykonawca.Free;
  Zamawiajacy.Free;
  inherited;
end;

procedure TDaneZamowienia.jakasmetoda;
var
  nowyZamawiajacy: TKontrahenci;
begin
  nowyZamawiajacy := TKontrahenci.Create;
  nowyZamawiajacy.nip:='9999999999';
  Zamawiajacy.Add(nowyZamawiajacy);
end;
1

Ale zakładając że z jakiegoś powodu on potrzebuje mieć tablicę to można to zrobić tak:
Tworzenie:

SetLength(Zamawiajacy, XXX);
for i := 0 to XXX - 1 do
  Zamawiajacy[i] := TKontrahenci.Create();

Tu idealnie widać problem z nazewnictwem zmiennych/klas. Raz "Zamawiajacy", raz "Kontrahenci". To proszenie się o kłopoty na przyszłość.

Zwalnianie:

for i := 0 to XXX - 1 do
  Zamawiajacy[i].Free;
SetLength(Zamawiajacy, NIL);

Jak dla mnie jedyną sytuacją gdy widzę zastosowanie takiego tworu to grzebanie w jakimś starym kodzie, gdzie... no dobra. Nie wiem :)

Normalnie do takich rzeczy polecam utworzyć sobie nowy typ (żeby nie powtarzać tego jak w powyższym przykładowym kodzie):

type
 TKontrahenci = TObjectList<TKontrahent>;

albo, jak to ma jeszcze coś robić w ramach wszystkich:

 type
 TKontrahenci = class(TObjectList<TKontrahent>)
 ...a tu np SaveToFile()
 end;

potem tylko

Kontrahenci := TKontrahenci.Create();

i można sobie zrobić Add, Delete, Insert, Sort... W tablicy trzeba się z tym mordować.

1

Przeczytałem wszystko i nie rozumiem początku tej dyskusji. Ale dlaczego nie wykorzystujesz DataSetu? Masz jakiś problem wydajnościowy - za dużo danych? Bo jeśli nie, to wszystko jest dziwne. Może po prostu wróć do początku i wykorzystaj istniejącą klasę np. TBufDataset. Na razie to kod wygląda na wyważanie otwartych drzwi, albo czegoś tu nie wyjaśniłeś.

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