[解決済み] 暗黙のインターフェイス変数に対するコンパイラの扱いは文書化されていますか?
質問
同じような質問を 質問 をしたことがあります。
この質問のソースは、コンパイラによって作成された暗黙のインターフェイス変数の存在を私が認識していなかったことによる、私のコード内のバグでした。この変数は、それを所有するプロシージャーが終了したときに確定されました。これは、次に、変数の寿命が私が予想していたよりも長かったためにバグを引き起こしました。
さて、私は、コンパイラーからのいくつかの興味深い動作を説明するための簡単なプロジェクトを持っています。
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の中のコードを別のメソッドに抽出し、インターフェイス変数のスコープを狭めることで問題を解決しました。
どのように解決したか?
この動作に関するドキュメントがあるとすれば、それはおそらく、関数の結果をパラメータとして渡すときに中間結果を保持するための一時変数をコンパイラが生成する領域でしょう。 このコードを考えてみましょう。
procedure UseInterface(foo: IInterface);
begin
end;
procedure Test()
begin
UseInterface(Create());
end;
コンパイラは、UseInterfaceに渡されるCreateの結果を保持するために、暗黙のtemp変数を作成しなければなりません。 この暗黙の一時変数は、それを所有するプロシージャの終わり、この場合は Test() プロシージャの終わりで廃棄されます。
ポインターの割り当てのケースは、コンパイラーは値がどこに行くかを見ることができないので、関数のパラメーターとして中間インターフェースの値を渡すのと同じバケツに落ちる可能性があります。
この領域では、長年にわたっていくつかのバグがあったことを思い出します。昔 (D3? D4?) は、コンパイラーは中間値をまったく参照カウントしませんでした。ほとんどの場合うまくいきましたが、パラメータ・エイリアスの状況で問題が発生しました。この問題が解決された後、const paramsに関するフォローアップが行われたと思います。中間値インターフェイスの廃棄を、それが必要とされるステートメントの後にできるだけ早く移動させたいという要望は常にありましたが、コンパイラがステートメントやブロック単位で廃棄を処理するように設定されていなかったため、Win32オプティマイザに実装されることはなかったと思われます。
関連
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン