1. ホーム
  2. delphi

[解決済み] 暗黙のインターフェイス変数に対するコンパイラの扱いは文書化されていますか?

2023-05-23 06:38:33

質問

同じような質問を 質問 をしたことがあります。

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

さて、私は、コンパイラーからのいくつかの興味深い動作を説明するための簡単なプロジェクトを持っています。

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オプティマイザに実装されることはなかったと思われます。