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

时间:2012-10-11 10:08:16

标签: string delphi delphi-xe2 refcounting

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

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

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;

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

  1. 结果:AnsiString - > LStrAsg
  2. 结果:UnicodeString: - > UStrAsg
  3. Local var:UnicodeString: - > UStrLAsg
  4. Local var:AnsiString: - > LStrLAsg
  5. 虽然后两个是按预期克隆指针,但前两个是将字符串复制到新实例,就像我向它们添加UniqueString调用一样。

    为什么会有这种差异?

2 个答案:

答案 0 :(得分: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;
....

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

您有两个功能:

  • LStrAsgUStrAsg当字符串有机会成为常量时由编译器生成 - 这是上面的代码;
  • LStrLAsgUStrLAsg(添加L代表“local”)当源字符串是本地时由编译器生成,因此不是常量:在这种情况下,P.refCnt < 0将不会被选中,因此它会比上面的代码更快。

答案 1 :(得分:1)

在与 David Heffernan 讨论之后,我开始认为Delphi编译器不知道它赋给变量的值是多少。有地方的那种“类型擦除”。它无法从本地堆栈变量和本地字符串表达式告诉全局常量。在函数退出发生后,它无法判断源是否存在。虽然我们知道字符串文字或全局常量或任何具有与函数执行无关的生命周期 - 编译器只是丢失了该信息。相反,它起到了防御作用,并始终克隆价值 - 只是为了它不再存在的机会。我不确定,但这看起来很合理。虽然这个粗略的不加区别的代码规则的后果是Delphi中的另一个 gotcha : - (