暗黙的なインターフェース変数のコンパイラー処理は文書化されていますか?


86

少し前に、暗黙のインターフェイス変数について同様の質問をしました

この質問の原因は、コンパイラによって作成された暗黙のインターフェイス変数の存在を認識していなかったため、コードのバグでした。この変数は、それを所有するプロシージャが終了したときに確定されました。これにより、変数の存続期間が予想よりも長くなるため、バグが発生しました。

これで、コンパイラの興味深い動作を説明する簡単なプロジェクトができました。

program ImplicitInterfaceLocals;

{$APPTYPE CONSOLE}

uses
  Classes;

function Create: IInterface;
begin
  Result := TInterfacedObject.Create;
end;

procedure StoreToLocal;
var
  I: IInterface;
begin
  I := Create;
end;

procedure StoreViaPointerToLocal;
var
  I: IInterface;
  P: ^IInterface;
begin
  P := @I;
  P^ := Create;
end;

begin
  StoreToLocal;
  StoreViaPointerToLocal;
end.

StoreToLocalあなたが想像するようにコンパイルされます。I関数の結果であるローカル変数は、暗黙のvarパラメーターとしてに渡されますCreate。片付けを行うStoreToLocalと、への呼び出しが1回になりますIntfClear。そこに驚きはありません。

ただし、StoreViaPointerToLocal扱いは異なります。コンパイラは、に渡す暗黙のローカル変数を作成しCreateます。にCreate戻ると、への割り当てP^が実行されます。これにより、インターフェイスへの参照を保持する2つのローカル変数がルーチンに残ります。片付けを行うStoreViaPointerToLocalと、への呼び出しが2回行われIntfClearます。

のコンパイル済みコードStoreViaPointerToLocalは次のようになります。

ImplicitInterfaceLocals.dpr.24: begin
00435C50 55               push ebp
00435C51 8BEC             mov ebp,esp
00435C53 6A00             push $00
00435C55 6A00             push $00
00435C57 6A00             push $00
00435C59 33C0             xor eax,eax
00435C5B 55               push ebp
00435C5C 689E5C4300       push $00435c9e
00435C61 64FF30           push dword ptr fs:[eax]
00435C64 648920           mov fs:[eax],esp
ImplicitInterfaceLocals.dpr.25: P := @I;
00435C67 8D45FC           lea eax,[ebp-$04]
00435C6A 8945F8           mov [ebp-$08],eax
ImplicitInterfaceLocals.dpr.26: P^ := Create;
00435C6D 8D45F4           lea eax,[ebp-$0c]
00435C70 E873FFFFFF       call Create
00435C75 8B55F4           mov edx,[ebp-$0c]
00435C78 8B45F8           mov eax,[ebp-$08]
00435C7B E81032FDFF       call @IntfCopy
ImplicitInterfaceLocals.dpr.27: end;
00435C80 33C0             xor eax,eax
00435C82 5A               pop edx
00435C83 59               pop ecx
00435C84 59               pop ecx
00435C85 648910           mov fs:[eax],edx
00435C88 68A55C4300       push $00435ca5
00435C8D 8D45F4           lea eax,[ebp-$0c]
00435C90 E8E331FDFF       call @IntfClear
00435C95 8D45FC           lea eax,[ebp-$04]
00435C98 E8DB31FDFF       call @IntfClear
00435C9D C3               ret 

コンパイラがこれを行っている理由は推測できます。結果変数に割り当てても例外が発生しないことが証明できる場合(つまり、変数がローカルの場合)、結果変数を直接使用します。それ以外の場合は、暗黙的なローカルを使用し、関数が返されるとインターフェイスをコピーするため、例外が発生した場合に参照がリークすることはありません。

しかし、私はこれについての記述をドキュメントで見つけることができません。インターフェイスの寿命は重要であり、プログラマーとして時々それに影響を与えることができる必要があるため、これは重要です。

それで、この振る舞いの文書があるかどうか誰かが知っていますか?そうでなければ、誰かがそれについてこれ以上の知識を持っていませんか?インスタンスフィールドはどのように処理されますか、まだ確認していません。もちろん、すべて自分で試すこともできますが、より正式なステートメントを探しており、試行錯誤によって作成された実装の詳細に依存することは常に避けたいと考えています。

アップデート1

Remyの質問に答えるために、別のファイナライズを実行する前に、インターフェイスの背後にあるオブジェクトをファイナライズする必要があるのはいつかが重要でした。

begin
  AcquirePythonGIL;
  try
    PyObject := CreatePythonObject;
    try
      //do stuff with PyObject
    finally
      Finalize(PyObject);
    end;
  finally
    ReleasePythonGIL;
  end;
end;

このように書かれているようにそれは大丈夫です。しかし、実際のコードでは、GILがリリースされて爆撃された後に完成した、2番目の暗黙のローカルがありました。Acquire / Release GIL内のコードを別のメソッドに抽出して問題を解決し、インターフェイス変数のスコープを狭めました。


8
質問が本当に複雑であることを除けば、なぜこれが反対票を投じられたのかわかりません。私の頭をはるかに超えていることに賛成。まさにこのちょっとした秘儀が、1年前に開発したアプリでいくつかの微妙な参照カウントのバグをもたらしたことを私は知っています。私たちの最高のオタクの一人はそれを理解するのに何時間も費やしました。結局、私たちはそれを回避しましたが、コンパイラがどのように機能するのか理解できませんでした。
ウォーレンP

3
@Sergコンパイラは参照カウントを完全に行いました。問題は、私が見ることができなかった参照を保持する余分な変数があったことでした。私が知りたいのは、コンパイラがそのような余分な隠された参照を取得するように促すものです。
David Heffernan

3
私はあなたを理解していますが、そのような余分な変数に依存しないコードを書くことをお勧めします。コンパイラにこれらの変数を好きなだけ作成させてください。堅実なコードはそれに依存すべきではありません。
kludg 2011年

2
これが起こっているときの別の例:procedure StoreViaAbsoluteToLocal; var I: IInterface; I2: IInterface absolute I; begin I2 := Create; end;
Ondrej Kelle 2011年

2
私はこれをコンパイラのバグと呼びたくなります...一時的なものはスコープから外れた後にクリアする必要があります。これ割り当ての終わりである必要があります(関数の終わりではありません)。そうしないと、あなたが発見したように微妙なエラーが発生します。
nneonneo 2012

回答:


15

この動作のドキュメントがある場合、関数の結果をパラメーターとして渡すときに中間結果を保持するのは、おそらく一時変数のコンパイラー生成の領域にあります。このコードを検討してください:

procedure UseInterface(foo: IInterface);
begin
end;

procedure Test()
begin
    UseInterface(Create());
end;

コンパイラーは、Createの結果がUseInterfaceに渡されるときにそれを保持するために、暗黙の一時変数を作成して、インターフェースの存続期間がUseInterface呼び出しの存続期間以上であることを確認する必要があります。その暗黙の一時変数は、それを所有するプロシージャの最後、この場合はTest()プロシージャの最後に破棄されます。

コンパイラーは値の行き先を「見る」ことができないため、ポインター割り当てのケースが、関数パラメーターとして中間インターフェース値を渡すのと同じバケットに分類される可能性があります。

この分野には何年にもわたっていくつかのバグがあったことを思い出します。ずっと前(D3?D4?)、コンパイラは中間値をまったく参照カウントしませんでした。ほとんどの場合は機能しましたが、パラメータエイリアスの状況で問題が発生しました。それが解決されると、constparamsに関するフォローアップがあったと私は信じています。中間値のインターフェイスの破棄は、必要なステートメントの後できるだけ早く移動したいという要望が常にありましたが、コンパイラが設定されていないため、Win32オプティマイザに実装されたことはないと思います。ステートメントまたはブロックの粒度で廃棄を処理するためにアップします。


0

コンパイラが一時的に非表示の変数を作成することを決定しないことを保証することはできません。

そして、そうしても、オフにされた最適化(またはスタックフレーム?)は、完全にチェックされたコードを台無しにする可能性があります。

そして、プロジェクトオプションのすべての可能な組み合わせでコードをレビューすることに成功したとしても、Lazarusや新しいDelphiバージョンのようなものでコードをコンパイルすると地獄に戻ります。

最善の策は、「内部変数はルーチンよりも長生きできない」ルールを使用することです。コンパイラがいくつかの内部変数を作成するかどうかは通常わかりませんが、ルーチンが存在するときにそのような変数(作成されている場合)がファイナライズされることはわかっています。

したがって、次のようなコードがある場合:

// 1. Some code which may (or may not) create invisible variables
// 2. Some code which requires release of reference-counted data

例えば:

Lib := LoadLibrary(Lib, 'xyz');
try
  // Create interface
  P := GetProcAddress(Lib, 'xyz');
  I := P;
  // Work with interface
finally
  // Something that requires all interfaces to be released
  FreeLibrary(Lib); // <- May be not OK
end;

次に、「Workwithinterface」ブロックをサブルーチンにラップする必要があります。

procedure Work(const Lib: HModule);
begin
  // Create interface
  P := GetProcAddress(Lib, 'xyz');
  I := P;
  // Work with interface
end; // <- Releases hidden variables (if any exist)

Lib := LoadLibrary(Lib, 'xyz');
try
  Work(Lib);
finally
  // Something that requires all interfaces to be released
  FreeLibrary(Lib); // <- OK!
end;

これは単純ですが、効果的なルールです。


私のシナリオでは、I:= CreateInterfaceFromLib(...)が暗黙のローカルになりました。したがって、あなたが提案することは役に立ちません。いずれにせよ、私はすでに質問の回避策を明確に示しました。関数スコープによって制御されている暗黙のローカルの存続期間に基づくもの。私の質問は、暗黙のローカルにつながるシナリオに関するものでした。
David Heffernan 2015

私のポイントは、これはそもそも質問するのは間違った質問だということでした。
アレックス

1
あなたはその見方を歓迎しますが、コメントとしてそれを表現するべきです。質問の回避策を再現しようとする(失敗した)コードを追加することは、私には奇妙に思えます。
David Heffernan 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.