【问题标题】:string := const : why different implementation for local and result?string := const : 为什么本地和结果的实现不同?
【发布时间】:2012-10-11 10:08:16
【问题描述】:

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

字符串常量基本上是具有负引用计数器的变量,应该抑制自动内存[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 调用一样。

为什么会有这种差异?

【问题讨论】:

  • 这可能是由于具有局部范围的变量和具有在其中进行赋值的函数之外的范围的变量之间的差异。当您分配给Result 变量时,您正在分配一个在该函数之外可见的变量。当您分配给本地时,它是该函数私有的。
  • @David,是的,是的,但是“那又怎样?” !为什么禁止将外部变量指向“-1”常量?当(如果)需要时,它将按需分开。并且在函数中主动执行它看起来像是过早的优化,并且当“常识”没有区别时也会引入完全不同的行为。
  • 我只能认为编译器无法跟踪它分配“来自”的内容。它认为它分配的不是全局常量,而是堆栈上的内部表达式,这将丢失。然后立即克隆是有意义的。但这似乎太粗糙了,如此“全局”的规则以至于效率相当低。

标签: string delphi delphi-xe2 refcounting


【解决方案1】:

在 Delphi 中,常量字符串在分配给另一个 global 变量时总是被复制,而不是复制给 local 变量,以避免在某些临界情况下访问冲突。

使用来源,卢克!

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 代表“本地”)由编译器在源字符串为本地时生成,因此没有常量:在这种情况下,P.refCnt &lt; 0 不会被检查,所以会比上面的代码快。

【讨论】:

  • Arnaud - 看看 _UStrLAsg 和 _LStrLAsg。它们还用于将常量复制到字符串。他们不会克隆它们。虽然有时可能会使用您的 _LStrAsg,但有时 Delphi 会使用具有不同行为的另一个函数。为什么这种分裂发生在看起来基本相同的行为上,这是问题的核心。
  • DLL/BPL 卸载是公平的,但与 const 问题无关。 BPL-local 常量将与其 BPL 一起卸载。所以感谢您的提示,但您的答案不正确。我实际上展示了代码(至少在 XE2 中)颠覆 UStrLAsg... 当源字符串是本地的,所以没有常量 的想法 - 它实际上是调用字符串常量,无论是命名还是字面意思。
  • 更是如此,想象这样一个例程:procedure XXX; var s: string; h: THandle; begin h := LoadPackage('PK1.BPL'); s := PK1.Unit1.Const1; UnloadPackage(h); ShowMessage(s); end; 这一次 DLL 将被卸载,指向 const 的字符串指针变成了流浪者。我认为 Delphi 不会检测到这一点并通过自动克隆字符串来保护我。
  • P.refCnt &lt; 0 won't be checked 实际上也是不正确的。将始终检查它,但如果它是否定的 - 则将跳过 InterlockedIncrement,这实际上会增加多核环境中的速度。
  • 简历:您的报价是 Delphi RTL 源还是 FreePascal 之一?如果Delphi那么哪个版本?对其他 Pascal 编译器的研究似乎很有趣,但对 Delphi 则不然。
【解决方案2】:

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

【讨论】:

  • 任何事实评论?看起来我设法拥有了一个个人讨厌的粉丝:-D
  • 对不起,我应该留下评论。我对您投了反对票(并且不是“个人讨厌的粉丝”。)有两个原因:(a):您的回答具有误导性-编译器确实知道很多有关其变量的信息,以及使用哪种方法的选择内部是根据目的地制作的。 (你说得对,这不是来源。)Arnaud 的(赞成的)上面的回答详细说明了这一点并解释了原因,因为有一些临界案例。我也不认为将其称为“陷阱”是正确的。
  • (b) 另一个答案质量极高,深入研究了源代码,列出了所有可能的组合以及何时和为什么调用的内容 - 即,这是一个完美的答案。你的答案是“我开始认为......” - 不是一个高质量的答案。第二个原因是我投反对票的原因。
  • 感谢您的解释。 (a) Arnaud 的帖子有很多好主意,但实际上是不正确的,至少它与我在 XE2 上看到并在我的帖子中描述的行为相矛盾。 (b) 另一个答案似乎被删除了,不知道为什么。我现在不记得它的内容了,但我记得它没有回答。我的答案肯定不好,但我等了一个星期也没有答案。
  • Arnaud 的回答与事实并不矛盾。这是正确的答案。
猜你喜欢
  • 2020-01-03
  • 1970-01-01
  • 1970-01-01
  • 2017-07-08
  • 2023-04-11
  • 1970-01-01
  • 2020-09-17
  • 1970-01-01
相关资源
最近更新 更多