JRG
JRG

Reputation: 593

How to use MOVE for dynamic arrays with a record as an element and a dynamic array field on it?

I'm using Delphi Rio and my program has a lot of dynamic arrays operations. In order to improve speed of some long array copy, I tried to use Move . For 1D dynamic arrays of basic types (real, integer) I could manage the use of Move, but for a dynamic arrays with a record as its element and this record with another dynamic array fields I'm having trouble to make it work. The way I' using MOVE for the dynamic arrays fileds of records, after issue the command , these fileds continue pointing to the original source address of the source array.

Testing procedure : array_A is dynamic array of records, tow fileds of this record are dynamic arrays. After issue Move(array_A[0],B_array[0],sizeof(Array[0]) * length(arra_A)) then change values in array_B dynamic arrays fields, this cause changes in values of array_A too ! Both are pointing to the same address. How to change this behaviour using MOVE ?

See my code :

    program ProjArrayMove;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

Type

     Arrayint   = Tarray<Integer>;
     Arrayreal  = TArray<Real>;

     RECmember = record
           mcode       : integer;
           mname       : string[30];
           mdynarr_int  : arrayint;
           mdynarr_real : arrayreal;
     end;

     ArrayMember = array of RECMember;

     Procedure FillsRecord (var pRec : RECmember; pSize : integer; pFactor : real);
     // Given a pRec , fills each dynamic array fields with pSize numbers
     var
        idx : integer;
     begin
          setlength(pRec.mdynarr_int,pSize);
          setlength(pRec.mdynarr_real,pSize);
          with pRec do
          begin
               for idx := 0 to pSize - 1 do
               begin
                    mdynarr_int[idx]  := idx;
                    mdynarr_real[idx] := idx * (1.0 + pFactor);
               end;
          end;
     end;


     Procedure FillsArray  (var pArr : ArrayMember; pSizeArray, pSizeRec : integer; pChar : Char);
     // Given a array of records pArr  , fills the array with pSizeArray records
     var
        idx : integer;
     begin
           setlength(pArr,pSizeArray);

           //Fils first record - element 0
           FillsRecord(pArr[0],pSizeRec,2.3);
           pArr[0].mcode := 0;
           pArr[0].mname := '0' + pChar;

           //Fills major array with records , each record with dynamic arrays fields also filled
           for idx := 1 to High(pArr) do
           begin
                FillsRecord(pArr[idx],pSizeRec,idx * 2.3);
                pArr[idx].mcode := idx;
                pArr[idx].mname  := idx.ToString + StringOfChar(pChar,2);
           end;
     end;


     Procedure PrintArray(pArr : ArrayMember);
     // Given an array of records pArr, prints each element including the dynami array fields
     var
        i,j : integer;
     begin
           Writeln(stringofchar('=',20));
           for I := Low(pArr) to High(pArr) do
           begin
                 Write('Idx :' + i.ToString + ' ' );
                 Write('Code :' + pArr[i].mcode.ToString  + ' ' );
                 Write('Name :' + pArr[i].mname + '   ');
                 Write(' mdynarr_int :');
                 for j := Low(pArr[i].mdynarr_int) to High(pArr[i].mdynarr_int) do
                     Write(' ' + pArr[i].mdynarr_int[j].ToString);
                 Write(' mdynarr_real :');
                 for j := Low(pArr[i].mdynarr_real) to High(pArr[i].mdynarr_real) do
                     Write(' ' + FloatToStr(pArr[i].mdynarr_real[j]));
                 Writeln;
           end;
     end;

var
    larray_A, larray_B : ArrayMember;
    idx, lsizeA, lsize_int, lsize_real : integer;
begin
      try
          //Step 1 - Fills the first array with 10 records and its relaed dynamic arrays fields with 5 elements each
          FillsArray(larray_A,10,5, 'A');

          //Step 2- An attempt to ast copy elements of larray_A to larray_B
          Setlength(larray_B, length(larray_A));
          lsizeA := Sizeof(larray_A[0]);

          MOVE(larray_A[0], larray_B[0], length(larray_A) * lsizeA);

          Writeln('========== ARRAY A ==========');

          PrintArray(larray_A);
          readln;

          Writeln('========== ARRAY B ==========');

          PrintArray(larray_B);
          readln;

          //Now change values in item of array B
          larray_B[0].mcode := 777;
          larray_B[0].mname := 'B was changed';
          larray_B[0].mdynarr_int[0]  := 427;
          larray_B[0].mdynarr_real[0] := 784.96;

          Writeln('======= ARRAY B AFTER CHANGES ========');

          PrintArray(larray_B);
          readln;

          Writeln('====== VERIFYING IMPACT IN ARRAY A =======');

          PrintArray(larray_A);
          readln;

          //3-Attemp to use MOVE to copy contet of dynamic arrays fields in records member
          lsize_int := Sizeof(larray_A[0].mdynarr_int[0]);
          lsize_real:= Sizeof(larray_A[0].mdynarr_real[0]);

          for idx := Low(larray_B) to High(larray_B) do
          begin
                setlength(larray_B[idx].mdynarr_int,length(larray_A[idx].mdynarr_int));
                **MOVE(larray_A[idx].mdynarr_int[0],larray_B[idx].mdynarr_int[0],
                     lsize_int * length(larray_B[idx].mdynarr_int) );**

                setlength(larray_B[idx].mdynarr_real,length(larray_A[idx].mdynarr_real));
                MOVE(larray_A[idx].mdynarr_int[0],larray_B[idx].mdynarr_int[0],
                     lsize_real * length(larray_B[idx].mdynarr_real) );
          end;

          Writeln;
          Writeln;

          //Now change values in item of array B
          larray_B[0].mcode := 555;
          larray_B[0].mname := '2nd Change in B';
          larray_B[0].mdynarr_int[0]  := 10427;
          larray_B[0].mdynarr_real[0] := 10784.96;

          Writeln('======= ARRAY B AFTER 2nd CHANGES ========');

          PrintArray(larray_B);
          readln;

          Writeln('====== VERIFYING IMPACT IN ARRAY A =======');

          PrintArray(larray_A);
          readln;

          Writeln('==> It seems that MOVE in record fields of dynamic array type does not work as I expected - WHY ?');
          readln;

      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
end.

Thank you all !

Upvotes: 3

Views: 1385

Answers (2)

David Heffernan
David Heffernan

Reputation: 613461

Your first call to Move takes a copy of the inner array references but does behind the back of the compiler and therefore disrupts reference counting. From that point on everything else that you do it doomed to fail. You cannot use Move on data that is managed.

To do this correctly you need code like this:

type
  TMyRec = record
    Int: Integer;
    Arr: TArray<Integer>;
    function Clone: TMyRec;
  end;

function TMyRec.Clone: TMyRec;
begin
  Result.Int := Int;
  Result.Arr := Copy(Arr);
end;

function CloneRecArray(const Arr: array of TMyRec): TArray<TMyRec>;
var
  i: Integer;
begin
  SetLength(Result, Length(Arr));
  for i := 0 to High(Result) do
    Result[i] := Arr[i].Clone;
end;

Upvotes: 5

MBo
MBo

Reputation: 80287

Have you explored internals of copy operation for dynamic arrays - _DynArrayCopy in system.pas?

It checks type of all fields of internal data structures, copies simple data with Move, and recursively calls itself for dynamic arrays fields.

Perhaps you final result will be alike this.

Upvotes: 3

Related Questions