将一个动态数组记录的变量复制到另一个,但它们共享同一个地址。

huangapple go评论59阅读模式
英文:

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

问题

Modifying one variable affects another because in your code, both a and b are referencing the same memory location initially when you use CopyMemory. This means that when you modify the contents of a, it also reflects in b because they are essentially pointing to the same data in memory. If you want a and b to be independent of each other, you should make a deep copy of a into b, ensuring that they each have their own separate memory allocations.

英文:
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?

答案1

得分: 4

A dynamic array is a reference type. 实际数组存储在内存的其他地方,任何引用它的数组变量只是指向数组内存的指针。

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). 您使用的 CopyMemory() 仅仅是从一个变量复制一个指针到另一个变量,所以你使得两个变量指向内存中相同的物理数组(并泄漏了另一个数组)。

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. 还请注意,动态数组是有引用计数的,但是你绕过了编译器对引用计数的管理。因此,你最终得到的是两个变量都指向相同的数组,但它的引用计数被存储为1,而不是2。因此,当其中一个变量超出范围时,RTL 将认为该变量是最后的引用,并会从内存中释放数组,留下另一个变量悬空。

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: 若要使用 CopyMemory() 将一个动态数组的 内容 复制到另一个数组,而不仅仅是复制源数组的 指针,你需要 解引用 数组指针以访问数组的第一个元素,例如:

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));  
  //或者:
  //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. 但是,通过这样做,当你复制每个 TMen 实例的 string 字段的原始字节时,你会遇到类似的问题,就像复制数组变量的原始字节时一样。就像动态数组一样,string 也是一个引用类型(即指向存储在其他地方的内存的指针),它使用引用计数进行内存管理。

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. However, if you had any runtime-created string values, thus their reference counts were greater than 0, you would run into problems with dangling pointers. 在你的特定示例中,所有的字符串值都是编译时常量,所以它们的引用计数是 -1,因此它们不会为其分配或释放动态内存。但是,如果你有任何在运行时创建的字符串值,它们的引用计数大于 0,你将遇到悬空指针的问题。

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. 在这种情况下,ab 指向内存中相同的数组(其引用计数设置为2),所以修改 a[0].(code|name) 也会影响 b[0].(code|name) 等。

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: 否则,深拷贝 动态数组(即,物理上制作其所有数据的新副本)的方法是使用编译器的 [Copy()](https://docwiki.embarcadero.com/Libraries/en

英文:

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 <sup>1</sup>. 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.

<sup>1</sup>: 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:=&#39;code1&#39;;
  a[0].name:=&#39;name1&#39;;
  a[1].code:=&#39;code2&#39;;
  a[1].name:=&#39;name2&#39;;
  a[2].code:=&#39;code3&#39;;
  a[2].name:=&#39;name3&#39;;

  b := a;

  a[0].code:=&#39;aaaa&#39;;
  a[0].name:=&#39;bbbb&#39;;

  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:=&#39;code1&#39;;
  a[0].name:=&#39;name1&#39;;
  a[1].code:=&#39;code2&#39;;
  a[1].name:=&#39;name2&#39;;
  a[2].code:=&#39;code3&#39;;
  a[2].name:=&#39;name3&#39;;

  b := Copy(a);

  a[0].code:=&#39;aaaa&#39;;
  a[0].name:=&#39;bbbb&#39;;

  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.

huangapple
  • 本文由 发表于 2023年5月18日 09:00:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76277109.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定