delphi 复制一个变量的动态数组的记录到另一个,但他们共享一个地址

ifsvaxew  于 2023-05-22  发布在  其他
关注(0)|答案(1)|浏览(474)
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;

结果:

code:aaaa  name:bbbb
code:code2  name:name2
code:code3  name:name3
---------------------
code:aaaa  name:bbbb
code:code2  name:name2
code:code3  name:name3
---------------------

为什么修改一个变量会影响另一个变量?

ttisahbt

ttisahbt1#

动态数组是一种引用类型。实际的数组存储在内存中的其他地方,任何引用它的数组变量都只是指向数组内存的指针。
使用CopyMemory()只是将一个指针从一个变量复制到另一个变量,因此您使这两个变量指向内存中的同一个物理数组(并泄漏另一个数组)。
还要注意,动态数组是引用计数的,但您绕过了编译器对引用进行计数的功能。因此,最终有两个变量引用同一个数组,但其引用计数存储为1,而不是2。因此,当其中一个变量超出范围时,RTL将认为该变量是最后一次引用,并将从内存中释放数组,使另一个变量悬空。
要使用CopyMemory()将动态数组的 content 复制到另一个数组,而不仅仅是将 pointer 复制到源数组,您需要 dereference 数组指针以访问数组的第一个元素,例如:

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;

但是,这样做的话,在复制每个TMen示例的string字段的原始字节时,就会遇到类似的问题,就像复制数组变量的原始字节一样。就像动态数组一样,string也是一种引用类型(即,指向存储在其他地方的内存的指针),它使用引用计数进行内存管理1。因此,这种原始复制使得b中的所有string字段引用内存中a字段引用的相同string对象,但没有正确管理它们的引用计数。
1:然而,在您的特定示例中,所有字符串值都是编译时常量,因此它们的引用计数为-1,因此不会为它们分配或释放动态内存。但是如果您有任何运行时创建的字符串值,因此它们的引用计数> 0,您将遇到悬挂指针的问题。
也就是说,shallow-copy动态数组(即简单地复制其引用)的 * 正确 * 方法是将一个数组变量分配给另一个数组变量,并让编译器管理引用数组的引用计数:

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;

在这种情况下,ab引用内存中的同一个数组(其引用计数设置为2),因此修改a[0].(code|name)也会影响b[0].(code|name)等。
否则,要 deep-copy一个动态数组(即物理地复制其所有数据),请使用编译器的Copy()内部函数:

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;

在这种情况下,ab引用内存中完全不同的数组(每个数组的引用计数都设置为1),因此修改a[0].(code|name)不会影响b[0].(code|name)等,因为它们引用内存中的不同字符串。

相关问题