Delphi通过引用或值/副本传递参数

Vit*_*.us 10 delphi pascal pointers pass-by-reference

背景1

var text:String;

text:='hello';

myFunc(text);
Run Code Online (Sandbox Code Playgroud)

上下文2

function myFunc(mytext:String);
var textcopy:String;
begin

    textcopy:=mytext;

end;
Run Code Online (Sandbox Code Playgroud)

myFunc从Context1调用Context2,局部变量mytext指向Context2之外的内存?或者在mytext范围内有自己的内存空间,并用相同的内容填充/复制text?我可能遗漏了一些非常基本的东西,因为我收到了一个access violation错误.

有没有办法明确指定一个函数是应该通过引用还是通过值接收参数,然后像C一样复制?我不确定我是怎么做的.

Nic*_*nes 23

Delphi字符串的内存管理有点不寻常.在调用myFunc(text)和分配之后textcopy := mytext,所有三个变量(text,mytexttextcopy)将指向相同的地址,即原始字符串的地址.

但是只要您使用其中一个变量对字符串进行更改,Delphi就会在后台克隆字符串,并将更改应用于副本.另外两个变量仍指向原始变量,因此它们保持不变.因此,在Context 1中不会看到上下文2中所做的任何更改 - 这种"写时复制"机制有效地为您提供了按值传递的语义.所有这些字符串都是引用计数的,并且一旦所有引用都超出范围,将自动释放.

但是,有一个例外.如果使用指针而不是字符串操作访问字符串,则可以绕过复制步骤,更改将影响原始字符串.您还将绕过引用计数逻辑,并可能最终得到一个指向已释放的内存块的指针.这可能是您违反访问权限的原因,但我不能说没有更多细节/更多代码.

如果您想要引用传递,请将您的函数声明为myFunc(var mytext: String).如果你想强制Delphi复制字符串,而不是等到它被修改,你可以使用System.UniqueString.

  • @David:我不知道,听起来像他对调用语义和内存分配的掌握程度一般,而不是他们如何在Delphi中专门工作.虽然我当然同意通常更好地掩盖细节并让Delphi发挥其魔力,但我认为访问违规将需要更低级别的解释. (2认同)

Hen*_*röm 14

在Delphi中,string是一种通常类似于值类型的引用类型.它在堆上分配(不像大多数值类型那样堆栈),并具有自动引用计数和写时复制语义.

要理解这意味着什么,请考虑正常值类型(例如Integer)在作为参数传递给过程时的行为:

var
  gValue: Integer;

procedure PassByValue(aValue: Integer);
begin
  // Here @gValue <> @aValue
  aValue := aValue + 2;
  // Here @gValue <> @aValue
end;

procedure PassByRefrenceInOut(var aValue: Integer);
begin
  // Here @gValue = @aValue
  aValue := aValue + 2;
  // Here @gValue = @aValue
end;

procedure CallProcedures;
begin
  gValue := 0; 
  PassByValue(gValue);
  // gValue is still 0
  PassByReferenceInOut(gValue);
  // gValue is 2
end;
Run Code Online (Sandbox Code Playgroud)

PassByReferenceInOut中的var参数等效于将指针传递给参数的C约定.

相同的语义适用于字符串参数传递,但值的内部表示存在细微差别:

var
  gValue: string;

procedure PassByValue(aValue: string);
begin
  // Here PChar(gValue) = PChar(aValue) <<<<
  aValue := aValue + '2';
  // Here PChar(gValue) <> PChar(aValue)
end;

procedure PassByRefrenceInOut(var aValue: string);
begin
  // Here PChar(gValue) = PChar(aValue)
  aValue := aValue + '2';
  // Here PChar(gValue) = PChar(aValue)
end;

procedure CallProcedures;
begin
  gValue := ''; 
  PassByValue(gValue);
  // gValue is still ''
  PassByReferenceInOut(gValue);
  // gValue is '2'
end;
Run Code Online (Sandbox Code Playgroud)

如果要确保过程在其自己的字符串值副本上运行,请使用UniqueString过程,例如:

procedure PassByValue(aValue: string);
begin
  // Here PChar(gValue) = PChar(aValue)
  UniqueString(aValue);
  // Here PChar(gValue) <> PChar(aValue)
  aValue := aValue + '2';
  // Here PChar(gValue) <> PChar(aValue)
end;
Run Code Online (Sandbox Code Playgroud)

  • 最好先解释一下简单数据类型的行为. (2认同)

Mik*_*e W 9

在Delphi中通过引用传递你明确添加var关键字:

procedure myFunc(var mytext:String);
Run Code Online (Sandbox Code Playgroud)

这意味着myFunc可以修改字符串的内容并让调用者看到更改.