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

标签 delphi move copymemory

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
---------------------

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

最佳答案

动态数组是一种引用类型。实际的数组存储在内存中的其他位置,引用它的任何数组变量都只是指向数组内存的指针。

您对CopyMemory()的使用只是将一个指针从一个变量复制到另一个变量,因此您使这两个变量指向内存中的同一个物理数组(并泄漏另一个数组)。

另请注意,动态数组是引用计数的,但您绕过了编译器计算引用的能力。因此,最终有 2 个变量引用同一个数组,但其引用计数存储为 1,而不是 2。因此,当其中一个变量超出范围时,RTL 会认为该变量是最后一个引用并将释放内存中的数组,让另一个变量悬空。

使用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));  
  //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;

但是,通过这样做,当您复制 string 的原始字节时,您会遇到类似的问题。每个 TMen 的字段例如,当您复制数组变量的原始字节时。就像动态数组一样,string也是一种引用类型(即,指向存储在其他地方的内存的指针),它使用引用计数进行内存管理1。因此,这种原始复制使得所有 string b 中的字段引用同string内存中的对象 a的字段引用,但没有正确管理它们的引用计数。

1:但是,在您的特定示例中,所有字符串值都是编译时常量,因此它们的引用计数为-1,因此不会为它们分配或释放动态内存。但是,如果您有任何运行时创建的字符串值,因此它们的引用计数 > 0,您就会遇到悬空指针的问题。

话虽这么说,复制动态数组(即简单地复制其引用)的正确方法是将一个数组变量分配给另一个数组变量,然后让编译器管理引用数组的引用计数:

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)

否则,要深度复制动态数组(即以物理方式为其所有数据创建新副本),请使用编译器的 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)等,因为它们引用内存中的不同字符串。

关于delphi - 将动态记录数组的变量复制到另一个变量,但它们共享一个地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76277109/

相关文章:

Delphi 7 编译随着时间的推移变得越来越慢?

delphi - 调用泛型类型的方法?

delphi - Inno Setup ComponentsList OnClick事件

rust - 如何防止值被 move ?

html - Div 仅在缩小时 move

rust - 由于过滤 read_dir 输出,如何解决 "cannot move out of borrowed content"?

c++ - 将较小的数据 block 合并到一大块内存中

delphi - Delphi 2010的ribbon控件在应用程序菜单中有页脚内容?