Reputation: 593
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
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
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