少し前に、暗黙のインターフェイス変数について同様の質問をしました。
この質問の原因は、コンパイラによって作成された暗黙のインターフェイス変数の存在を認識していなかったため、コードのバグでした。この変数は、それを所有するプロシージャが終了したときに確定されました。これにより、変数の存続期間が予想よりも長くなるため、バグが発生しました。
これで、コンパイラの興味深い動作を説明する簡単なプロジェクトができました。
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内のコードを別のメソッドに抽出して問題を解決し、インターフェイス変数のスコープを狭めました。