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

问题描述 投票:0回答:1
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
---------------------

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

delphi move copymemory
1个回答
0
投票

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

您对

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

另请注意,动态数组是引用计数的,但您正在绕过编译器计算引用的能力。所以,你最终得到 2 个变量引用同一个数组,但它的引用计数存储为 1,而不是 2。因此,当其中一个变量超出范围时,RTL 将认为该变量是最后一个引用并将释放来自内存的数组,留下另一个变量悬空。

要将动态数组的 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));  
  //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),因此不会为它们分配或释放动态内存。但是如果你有任何运行时创建的字符串值(因此它们的引用计数 > 0),你就会遇到悬空指针的问题。

对动态数组进行复制的正确方法是简单地将一个变量分配给另一个变量并让编译器为您管理数组引用计数(这在您的示例中工作得很好,因为

string
有复制-写语义):

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;

否则,要 deep-复制动态数组,请使用编译器的

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;
© www.soinside.com 2019 - 2024. All rights reserved.