2011-10-13 3 views
83

あまりにも遅くない暗黙的なインタフェース変数について、私は同様のquestionを尋ねました。暗黙のインターフェイス変数のコンパイラ処理は文書化されていますか?

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

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の整頓は、IntfClearへの単一の呼び出しになります。そこに驚きはありません。

しかし、StoreViaPointerToLocalは異なって扱われます。コンパイラは暗黙のローカル変数を作成し、それをCreateに渡します。 Createが返ると、P^への代入が実行されます。これにより、ルーチンへの参照を保持する2つのローカル変数が残されます。 の整頓は、IntfClearを2回呼び出します。

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

アップデートは、私は別のファイナライズを行う前に、インターフェイスの背後にあるオブジェクトを完成させるために必要なときに、それは私には大事、レミーの質問に答えるために。

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

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

+7

質問が本当に複雑であることを除いて、これがなぜ落とされたのか分かりません。私の頭の上に道を行くためにアップアップされました。私が知っているのは、ちょうど1年前に仕事をしていたアプリでこのarcanumのビットが微妙な参照カウントのバグを引き起こしたことが分かっています。私たちの最高のオタクの1人は、それを理解する時間を費やしました。最終的には、コンパイラがどのように動作しようとしているのか理解できませんでした。 –

+0

参照カウントインターフェイスを操作するときは、独自のインターフェイス参照を持つ他のクライアント(あなたの場合はコンパイラ)がないと想定しないでください。すべてのクライアントが 'AddRef' /' Release'を正しく実行すれば、すべて正常に動作します。さもなければ、それはバグです(あなたのバグはコンパイラが参照カウントを正しく行うと仮定しているからです)。 – kludg

+3

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

答えて

13

この動作のドキュメントがある場合、関数結果をパラメータとして渡すときに、中間結果を保持するためのコンパイラの一時変数の作成領域に存在する可能性があります。このコードを考えてみましょう:インターフェースが寿命を持っていることを確認するために、UseInterfaceに渡されると、コンパイラが作成した結果を保持するために、暗黙の一時変数を作成する必要があり

procedure UseInterface(foo: IInterface); 
begin 
end; 

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

> = UseInterfaceコールの寿命。この暗黙的な一時変数は、それを所有するプロシージャの最後、この場合はTest()プロシージャの最後に配置されます。

コンパイラは、値がどこにあるのかを「見る」ことができないので、ポインタ代入ケースが中間のインターフェイス値を関数パラメータとして渡すのと同じバケットに入る可能性があります。

私は、この分野では長年にわたりいくつかのバグがありました。昔(D3-D4?)、コンパイラは中間値のカウントを全く参照しませんでした。それはほとんどの場合動作しましたが、パラメータエイリアスの状況で問題になりました。それが解決されると、const paramsに関するフォローアップがあったと私は信じている。必要な文の後にできるだけ早く中間値インタフェースの処分を移動することが常に望まれていましたが、コンパイラが設定されていないため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; 

を次に、あなただけのサブルーチンにブロック "インターフェースと協力し" ラップする必要があります

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; 

それは単純で効果的なルールです。

+0

私のシナリオでは、I:= CreateInterfaceFromLib ...)は、暗黙のローカルをもたらしました。だからあなたの提案は助けにならないでしょう。いずれにしても、私はすでにこの問題の回避策を明確に示しました。関数スコープによって制御される暗黙のローカルの存続期間に基づくもの。私の質問は暗黙の地元の人々につながるシナリオに関するものでした。 –

+0

私の主張は、まずこれが間違った質問であることでした。 – Alex

+0

あなたはその視点を歓迎しますが、それをコメントとして表現してください。質問の回避策を再現しようとする(失敗した)コードを追加することは、私にとっては奇妙なことです。 –

関連する問題