string:= const:为什么本地和结果的不同实现?

Ari*_*The 9 delphi string refcounting delphi-xe2

在Delphi中,函数结果经常被实现为var-parameter(尽管QC票证不是out-parameter).

字符串常量基本上是具有负refcounter的变量,它应该抑制自动内存[de]分配.http://docwiki.embarcadero.com/RADStudio/XE3/en/Internal_Data_Formats#Long_String_Types

它确实压制了它:下面的代码不会泄漏.

type
  TDealRecord = record
    id_Type: Integer;
    Price: extended;
    Remark: String;
  end;
const const_loop = 100000000;

function TestVar: TDealRecord;
//procedure TestVar;
var
  Li: Integer;
  LRec: TDealRecord;
begin
  for Li := 1 to const_loop do begin
     FillChar(Lrec,SizeOf(LRec), 0);
     LRec.Remark := 'Test';

//     FillChar(Result,SizeOf(Result), 0);
//     Result.Remark := 'Test';
  end;
end;
Run Code Online (Sandbox Code Playgroud)

但是改变操纵变量 - 它会立即开始大量泄漏.

function TestVar: TDealRecord;
//procedure TestVar;
var
  Li: Integer;
  LRec: TDealRecord;
begin
  for Li := 1 to const_loop do begin
//     FillChar(Lrec,SizeOf(LRec), 0);
//     LRec.Remark := 'Test';

     FillChar(Result,SizeOf(Result), 0);
     Result.Remark := 'Test';
  end;
end;
Run Code Online (Sandbox Code Playgroud)

事实证明,这string := const是通过不同的调用实现的,具体取决于LValue:

  1. 结果:AnsiString - > LStrAsg
  2. 结果:UnicodeString: - > UStrAsg
  3. Local var:UnicodeString: - > UStrLAsg
  4. Local var:AnsiString: - > LStrLAsg

虽然后两个是按预期克隆指针,但前两个是将字符串复制到新实例,就像我添加UniqueString调用它们一样.

为何如此差异?

Arn*_*hez 9

在Delphi中,常量字符串在分配给另一个全局变量时始终被复制,但不会复制到局部变量,以避免在某些边界情况下发生访问冲突.

使用来源,卢克!

System.pas中查看此代码提取:

{ 99.03.11
  This function is used when assigning to global variables.

  Literals are copied to prevent a situation where a dynamically
  allocated DLL or package assigns a literal to a variable and then
  is unloaded -- thereby causing the string memory (in the code
  segment of the DLL) to be removed -- and therefore leaving the
  global variable pointing to invalid memory.
}
procedure _LStrAsg(var dest; const source);
var
  S, D: Pointer;
  P: PStrRec;
  Temp: Longint;
begin
  S := Pointer(source);
  if S <> nil then
  begin
    P := PStrRec(Integer(S) - sizeof(StrRec));
    if P.refCnt < 0 then   // make copy of string literal
    begin
      Temp := P.length;
      S := _NewAnsiString(Temp);
      Move(Pointer(source)^, S^, Temp);
      P := PStrRec(Integer(S) - sizeof(StrRec));
    end;
    InterlockedIncrement(P.refCnt);
  end;
....
Run Code Online (Sandbox Code Playgroud)

因此,简而言之,通过设计,并避免在卸载DLL或包并且确实包含一些发送回主进程的常量值时的访问冲突,始终会生成本地副本.

你有两个功能:

  • LStrAsg或者UStrAsg当字符串有机会成为常量时由编译器生成 - 这是上面的代码;
  • LStrLAsg或者UStrLAsg(添加L代表"local"),当源字符串是本地时由编译器生成,因此不是常量:在这种情况下,P.refCnt < 0将不会被检查,因此它将比上面的代码更快.


Ari*_*The 2

在与David Heffernan讨论之后,我开始认为 Delphi 编译器只是不知道它分配给变量的值是什么。有点“类型擦除”的地方。它无法区分全局常量和局部堆栈变量和局部字符串表达式。它无法判断函数退出后源是否存在。虽然我们知道它是字符串文字或全局常量或任何具有独立于函数执行的生命周期的东西 - 编译器只是丢失了该信息。相反,它采取防御性的态度,总是克隆价值——只是为了让它不复存在。我不确定,但这看起来很合理。尽管这种粗暴的不加区别的代码生成规则的后果是Delphi 中的又一个陷阱:-(

  • 抱歉,我应该发表评论。我对你投了反对票(并且不是“个人讨厌的粉丝”。)有两个原因:(a):你的答案具有误导性 - 编译器确实知道有关其变量的大量信息,以及使用哪种方法的选择内部是根据目的地制作的。(你是对的,这不是来源。)上面阿诺(已投票)的答案详细介绍了这一点并解释了原因,因为存在一些边界情况。我认为称其为“陷阱”也不正确。 (2认同)