9

在 Delphi 函数中,结果经常被实现为 var 参数(尽管有 QC 票,但不是超出参数)。

字符串常量基本上是具有负 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. 本地变量:UnicodeString:-> UStrLAsg
  4. 本地变量:AnsiString:-> LStrLAsg

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

为什么会有这种差异?

4

2 回答 2

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 或包并且确实包含一些发送回主进程的常量值时发生访问冲突,总是制作本地副本。

你有两个功能:

  • LStrAsg或者UStrAsg当字符串有机会成为常量时由编译器生成 - 这是上面的代码;
  • LStrLAsgUStrLAsg(添加L代表“本地”),当源字符串为本地时由编译器生成,因此没有常量:在这种情况下,P.refCnt < 0不会检查,因此它会比上层代码更快。
于 2012-10-11T11:56:19.927 回答
2

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

于 2012-10-11T11:21:44.577 回答