pangzhenguang
pangzhenguang

Reputation: 31

Copy a variable of dynamic array of record to another, but they share an address

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

Answers (1)

Remy Lebeau
Remy Lebeau

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

Related Questions