9

Delphi 関数では、結果が var-parameter として実装されることがよくあります(QC チケットにもかかわらず out-parameter ではありません)。

文字列定数は、基本的に負の refcounter を持つ変数であり、自動メモリの [割り当て解除] を抑制する必要があります。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 := constLValue に応じて、さまざまな呼び出しで実装されていることがわかります。

  1. 結果: AnsiString -> LStrAsg
  2. 結果: UnicodeString: -> UStrAsg
  3. ローカル変数: UnicodeString: -> UStrLAsg
  4. ローカル変数: AnsiString: -> LStrLAsg

後者の 2 つは期待どおりにポインターを複製していますが、前の 2 つは、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 またはパッケージがアンロードされ、メイン プロセスに送り返される定数値が含まれていた場合のアクセス違反を回避するために、常にローカル コピーが作成されます。

次の 2 つの機能があります。

  • LStrAsgまたはUStrAsg、文字列が定数になる可能性がある場合にコンパイラによって生成されます - これは上記のコードです。
  • LStrLAsgソース文字列がローカルである場合にコンパイラによって生成されるor UStrLAsg(追加されLたのは「ローカル」の略) であるため、定数ではありP.refCnt < 0ません。この場合、チェックされないため、上のコードよりも高速になります。
于 2012-10-11T11:56:19.927 に答える
2

David Heffernanと話し合った結果、Delphi コンパイラは変数に割り当てる値を認識していないだけだと思い始めています。場所を持つ一種の「型消去」。ローカルのオンスタック変数とローカルの文字列式からグローバル定数を区別することはできません。関数の終了後にソースが存在するかどうかはわかりません。それが文字列リテラルまたはグローバル定数、または関数の実行とは無関係の有効期間を持つものであることはわかっていますが、コンパイラはその情報を失うだけです。代わりに、それは守備的な役割を果たし、常に値を複製します - それが存在しなくなる可能性のためだけに。確かではありませんが、それは合理的に見えます。この大雑把で無差別な codegen ルールの結果は、Delphi でのもう 1 つの落とし穴です:-(

于 2012-10-11T11:21:44.577 に答える