Przypisywanie rekordów, gdzie pola rekordu to tablice dynamiczne

0

Siema, moje pytanie jest potraktowane w pewnym sensie lenistwem i brakiem czasu, bo wiem, że sam se to mogę sprawdzić, więc jeśli mnie wyzwiecie, to przeżyję. Ale może ktoś na szybko będzie w stanie odpowiedzieć. ;) Przy okazji będzie coś dla potomnych.

Załóżmy, że mamy rekord, w skład którego wchodzą tablice dynamiczne i inne rekordy. Np:

TRec = record
  arr: array of integer;
  arr2: array of string; 
  subRec: TSubRecord;
end;

Teraz mamy dwie zmiennego tego typu, które chcemy przyrównać. I tu się zaczyna moje pytanie. Czy przyrównanie:
rec1:=rec2
jest bezpieczne?

Załóżmy, że rec1 występuje w parametrze funkcji (jako var), a rec2 jest zmienną lokalną tejże funkcji. Wg mnie rec1 będzie wskazywał na rec2(ewentualnie elementy rec1 będą wskazywać na elementy rec2) i po wyjściu z funkcji wszystko może szlag trafić. Gdyby rekordy składały się tylko z typów prostych, wszystko byłoby w porządku i można byłoby to zrobić. Dobrze rozumuję?

Tak więc, jeśli mam rację, to czy jedynym bezpiecznym sposobem jest przyrównywanie wszystkiego po kolei?

//pomijam sprawdzenie, czy tablice są puste
setLength(rec1.arr, length(rec2.arr));
copyMemory(@rec1.arr[0], @rec2.arr[0], length(rec2.arr));

setLength(rec1.arr2, length(rec2.arr2));
copyMemory(@rec1.arr2[0], @rec2.arr2[0], length(rec2.arr2));

rec1.subRec....... //itd

oczywiście, mogę skorzystać z dobrodziejstw nowoczesności i wyposażyć swój rekord w metodę Assign.
Czy nie ma jednak innej możliwości? Czy można w jakiś sprytny sposób skopiować cały rekord? Bo z tego, co widziałem, to rekordy z takimi polami(które nie są typami prostymi) nie są strukturami ciągłymi.

Jeśli sprawy wyglądają różnie w różnych środowiskach, z chęcią poznam te różnice.

0

Teraz mamy dwie zmiennego tego typu, które chcemy przyrównać. I tu się zaczyna moje pytanie. Czy przyrównanie:
rec1:=rec2
jest bezpieczne?

A od kiedy operator := to operator porównania?

Załóżmy, że rec1 występuje w parametrze funkcji (jako var), a rec2 jest zmienną lokalną tejże funkcji. Wg mnie rec1 będzie wskazywał na rec2(ewentualnie elementy rec1 będą wskazywać na elementy rec2) i po wyjściu z funkcji wszystko może szlag trafić.

Bzdura, musiałbyś do funkcji podawać wskaźniki na te rekordy i wtedy wykonać przypisanie, żeby wartości się nie zmieniły, a jedynie adres na jaki wskazuje;

Tak więc, jeśli mam rację, to czy jedynym bezpiecznym sposobem jest przyrównywanie wszystkiego po kolei?

Przyjemności sprawdzenia czy zadziała nie będę Cię pozbawiał - zamiast pisać posta mogłeś się upewnić pisząc krótszy program testowy;

Czy można w jakiś sprytny sposób skopiować cały rekord?

Jeśli miałby jedynie typy proste (ze statycznym rozmiarem pól) to owszem - procedura Move i ogień; W tym wypadku każdy rekord tego typu miałby dokładnie ten sam rozmiar i jego pola w pamięci zapisany były by w jednym ciągu:

type
  TRec = record
    Name: String[20];
    Surname: String[20];
    Age: Byte;
  end;
  PRec = ^TRec;

var
  prFoo, prBar: PRec;
begin
  New(prFoo);
  New(prBar);
  try
    prFoo^.Name := 'Foo';
    prFoo^.Surname := 'Pascal';
    prFoo^.Age := 88;

    prBar^.Name := 'Bar';
    prBar^.Surname := 'Delphi';
    prBar^.Age := 12;

    WriteLn('Before move:');
    WriteLn(prFoo^.Name, ', ', prFoo^.Surname, ', ', prFoo^.Age);
    WriteLn(prBar^.Name, ', ', prBar^.Surname, ', ', prBar^.Age, #10);

    Move(prFoo^, prBar^, SizeOf(TRec));

    WriteLn('After move:');
    WriteLn(prFoo^.Name, ', ', prFoo^.Surname, ', ', prFoo^.Age);
    WriteLn(prBar^.Name, ', ', prBar^.Surname, ', ', prBar^.Age);
  finally
    Dispose(prFoo);
    Dispose(prBar);
    ReadLn;
  end;
end.

na wyjściu otrzymamy:

Before move:
Foo, Pascal, 88
Bar, Delphi, 12

After move:
Foo, Pascal, 88
Foo, Pascal, 88

W przypadku struktur dynamicznie zmieniających swój rozmiar sprawa się komplikuje, bo nie można przepisać całego ot tak.

0
furious programming napisał(a):

Załóżmy, że rec1 występuje w parametrze funkcji (jako var), a rec2 jest zmienną lokalną tejże funkcji. Wg mnie rec1 będzie wskazywał na rec2(ewentualnie elementy rec1 będą wskazywać na elementy rec2) i po wyjściu z funkcji wszystko może szlag trafić.

Bzdura, musiałbyś do funkcji podawać wskaźniki na te rekordy i wtedy wykonać przypisanie, żeby wartości się nie zmieniły, a jedynie adres na jaki wskazuje;
</quote>

Chyba się nie rozumiemy (a przynajmniej ja). Załóżmy taką sytuację:

procedure GetRec(var rec: TRecord);
var
  r: TRecord;
  i: integer;
begin
  setLength(r.arr, 10); //arr to tablica dynamiczna
  for i:=low(r.arr) to high(r.arr) do r.arr[i]:=i;

  rec:=r;
end;

I ja mówię, że w takim przypadku, po wyjściu z funkcji wszystko się popsuje, bo przekazany parametr REC będzie już wskazywał na coś błędnego. Natomiast wszystko by działało OK, gdyby TRecord składał się tylko i wyłącznie z typów prostych. Tak jest?

0

@Juhas - podałem jedynie przykład dla rekordu, którego rozmiar jest stały i niezmienny - na nim można wykonać kopiowanie przez Move czy CopyMemory i zwykłe przypisanie przez operator := także;

Co do Twojego przykładu to nie jestem pewny, ale najprawdopodobniej mogłoby pójść coś nie tak, jeśli rozmiar macierzy różniłby się; A może nie - nie wiem dokładnie, więc trzeba to sprawdzić; Ja mam to zrobić, czy Ty chcesz? :]

1
Juhas napisał(a)

I ja mówię, że w takim przypadku, po wyjściu z funkcji wszystko się popsuje, bo przekazany parametr REC będzie już wskazywał na coś błędnego.

Typy rekordowe, ciągi znaków, interfejsy oraz tablice dynamiczne są refcountowane - w momencie wyjścia z funkcji zmniejszany jest licznik każdej zmiennej takiego typu, a gdy osiągnie on wartość zero, rekord/string/interfejs/tablica zostanie usunięty/zwolniony /-a.
Przy deklarowaniu zmiennej typu refcountowanego, na początku funkcji jej licznik jest ustawiany na jeden, a przy przypisaniach typu:
a := b;
Licznik a zostaje zmniejszony, podczas gdy b powiększony.
Zatem po wyjściu z funkcji r będzie miało licznik o wartości 1, czyli nie zostanie usunięte, a referencja pozostanie poprawna.
W przypadku rekordów natomiast wykonywane jest coś na wzór memcpy (patrz mój post niżej) - nie zostaje przypisana referencja, a zawartość rekordu, więc nie ma się czego obawiać.

O to chodzi?


Edit: fixnąłem parę rzeczy (:P).
0

Tak, o to chodzi. Możesz mi jeszcze powiedzieć od kiedy tak jest? Wiem, że stringi mają reference count od XE albo 2007. Ale, że tablice dynamiczne i rekordy? Tego nie wiedziałem. Masz może jakiegoś linka, jak w pamięci wygląda struktura rekordu? Na stronach embarcadero znalazłem informacje jedynie co do stringa.

0
Juhas napisał(a)

Możesz mi jeszcze powiedzieć od kiedy tak jest?

Jakoś od zawsze, z tego co mi wiadomo.

Ale, że tablice dynamiczne i rekordy? Tego nie wiedziałem.

Nieco się machnąłem - rekordy nie są refcountowane (w każdym razie kod wynikowy z FPC nie wskazuje na to, aby były - co jest dziwne, ponieważ zawsze myślałem o rekordach jako "samozwalniających" oraz "posiadających licznik referencji", mea culpa), tablice dynamiczne jednak są (FPC_DYNARRAY_INCR_REF, FPC_DYNARRAY_DECR_REF w przypadku FPC).
Mimo wszystko taki kod:

Type TRecord =
     Record
      I: Integer;
     End;

Function Foo: TRecord;
Var Tab: TRecord;
Begin
 Tab.I := 512;

 Result := Tab;
End;

Begin
 Writeln(Foo.I);
End.

Jest jak najbardziej bezpieczny, ponieważ w momencie napisania Result := Tab; wykonywane jest przypisanie Result.Pole := Tab.Pole; (dla każdego pola rekordu osobno), a nie jedynie przypisanie referencji, co miałoby miejsce w przypadku obiektów.

2

Możesz sobie kopiować bez problemu:
http://ideone.com/ad34iH
Poczynając od Delphi5 już nie ma z tym problemów oprócz sytuacji takiej:

procedure P2(var R:TRecord);
begin
  R.Cos:=0;
end;

procedure P1(const R:TRecord);
begin
  P2(R);
end;

procedure P0;
var R:TRecord;
begin
  P1(R);
end;

Gdzieś czytałem że od dawna kompilator powinien na to się buntować ale niestety tak nie jest.
I z całą pewnością każdy się zgodzi że nie wiadomo co ma kompilator z tym fantem zrobić.

0

OK, sprawdziłem (Delphi 6) i potwierdzam. Tablice dynamiczne są reference counted. Jeśli rekord A zawiera w sobie inny rekord (B), to faktycznie wygląda to tak, jakby rekord A zawierał swoje pola + pola rekordu B (bez dodatkowego wskaźnika na rekord B). Czyli jeśli chodzi o przypisanie rekordów, które składają się nie tylko z typów prostych, jest to faktycznie bezpieczne. Przynajmniej w Delphi 6. Ale sądzę, że w późniejszych też nie powinno być z tym problemu (chociaż znalazłem jakiś opisany błąd na stronach Embarcadero związany z rekordami zawierającymi funkcję - szukajcie pod kątem @CopyRecord).

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