Reputation: 31
type
TMen=record
code:String;
name:String;
end;
TMenLst=array of TMen;
TForm10 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
a,b:TMenLst;
public
{ Public declarations }
procedure show(v:TMenLst);
end;
var
Form10: TForm10;
implementation
{$R *.dfm}
procedure TForm10.Button1Click(Sender: TObject);
begin
SetLength(a,3);
a[0].code:='code1';
a[0].name:='name1';
a[1].code:='code2';
a[1].name:='name2';
a[2].code:='code3';
a[2].name:='name3';
SetLength(b,3);
CopyMemory(@b,@a,SizeOf(a));
//Move(a, b, SizeOf(a));
a[0].code:='aaaa';
a[0].name:='bbbb';
show(a);
show(b);
end;
procedure TForm10.show(v: TMenLst);
var
I:integer;
begin
for I := Low(v) to High(v) do
Memo1.Lines.Add('code:'+a[I].code+' '+'name:'+a[I].name);
Memo1.Lines.Add('---------------------');
end;
result:
code:aaaa name:bbbb
code:code2 name:name2
code:code3 name:name3
---------------------
code:aaaa name:bbbb
code:code2 name:name2
code:code3 name:name3
---------------------
Why does modifying one variable affect another?
Upvotes: 2
Views: 228
Reputation: 597885
A dynamic array is a reference type. The actual array is stored elsewhere in memory, and any array variables that refer to it are just pointers to the array's memory.
Your use of CopyMemory()
is copying just a pointer from one variable to another, so you are making the 2 variables point at the same physical array in memory (and leaking the other array).
Also note that a dynamic array is reference counted, but you are bypassing the compiler's ability to count the references. So, you end up with 2 variables referring to the same array but its reference count is stored as 1, not 2. So, when one of the variables goes out of scope, the RTL will think that variable was the last reference and will free the array from memory, leaving the other variable dangling.
To use CopyMemory()
to copy the content of a dynamic array to another, rather than just copying the pointer to the source array, you would need to dereference the array pointers to access the 1st elements of the arrays, eg:
procedure TForm10.Button1Click(Sender: TObject);
begin
SetLength(a,3);
a[0].code:='code1';
a[0].name:='name1';
a[1].code:='code2';
a[1].name:='name2';
a[2].code:='code3';
a[2].name:='name3';
SetLength(b,Length(a));
CopyMemory(@b[0],@a[0],SizeOf(TMen)*Length(a));
//or:
//CopyMemory(Pointer(b),Pointer(a),SizeOf(TMen)*Length(a));
//Move(a[0], b[0], SizeOf(TMen)*Length(a));
//Move(Pointer(a)^, Pointer(b)^, SizeOf(TMen)*Length(a))
a[0].code:='aaaa';
a[0].name:='bbbb';
show(a);
show(b);
end;
However, by doing this, you then run into a similar problem when you are copying the raw bytes of the string
fields of each TMen
instance, as when you are copying the raw bytes of the array variables. Just like a dynamic array, a string
is also a reference type (ie, a pointer to memory stored elsewhere) that uses reference counting for memory management 1. So, this kind of raw copying makes all of the string
fields in b
refer to the same string
objects in memory that a
's fields refer to, but without managing their reference counts correctly.
1: However, in your particular example, all of your string values are compile-time constants, so their reference counts are -1, thus no dynamic memory is allocated or freed for them. But if you had any runtime-created string values, thus their reference counts were > 0, you would run into problems with dangling pointers.
That being said, the correct way to shallow-copy a dynamic array (ie, to simply copy its reference) is to just assign one array variable to another and let the compiler manage the reference counts of the referenced arrays:
procedure TForm10.Button1Click(Sender: TObject);
begin
SetLength(a,3);
a[0].code:='code1';
a[0].name:='name1';
a[1].code:='code2';
a[1].name:='name2';
a[2].code:='code3';
a[2].name:='name3';
b := a;
a[0].code:='aaaa';
a[0].name:='bbbb';
show(a);
show(b);
end;
In this case, a
and b
refer to the same array in memory (with its reference count set as 2), and so modifying a[0].(code|name)
also affects b[0].(code|name)
, etc.
Otherwise, to deep-copy a dynamic array (ie, to physically make new copies of all of its data), use the compiler's Copy()
intrinsic function:
procedure TForm10.Button1Click(Sender: TObject);
begin
SetLength(a,3);
a[0].code:='code1';
a[0].name:='name1';
a[1].code:='code2';
a[1].name:='name2';
a[2].code:='code3';
a[2].name:='name3';
b := Copy(a);
a[0].code:='aaaa';
a[0].name:='bbbb';
show(a);
show(b);
end;
In this case, a
and b
refer to completely different arrays in memory (each with its reference count set as 1), and so modifying a[0].(code|name)
does not affect b[0].(code|name)
, etc because they refer to different strings in memory.
Upvotes: 4