2010-11-27 22 views
5

CLRが参照型と多相をどのように実装しているかを理解しようとしています。私はDon BoxのEssential .Net Vol 1を参照しました。これは大部分のものを熱狂させる大きな助けとなります。しかし、私は、以下の問題で、私が立ち往生/混乱しています。callvirtはどのようにしてフードの下で動作しますか?

私は可能な限り問題を説明しようとします。 次のコード

class Base 
{ 
    public void m() 
    { 
     Console.WriteLine("Base.m"); 
    } 
} 
class Derived : Base 
{ 
    public void m() 
    { 
     Console.WriteLine("Derived.m"); 
    } 
} 

今すぐ、以下に示す主な方法のIL持つ単純なコンソールアプリケーションを検討し考えてみましょう。私は、オブジェクト参照として実行するために、このコードをNOTを期待していたILを理解するために、手動で、コンパイラによって作成され、ILAsm.exe

.class private auto ansi beforefieldinit Console1.Program 
     extends [mscorlib]System.Object 
{ 
    .method private hidebysig static void Main(string[] args) cil managed 
    { 
     .entrypoint 
     // Code size  44 (0x2c) 
     .maxstack 1 
     .locals init ([0] class Console1.Base d) 
     nop 
     newobj  instance void Console1.Base::.ctor() 
     stloc.0 
     ldloc.0 
     callvirt instance void Console1.Derived::m() 
     nop 
     call  string [mscorlib]System.Console::ReadLine() 
     pop 
     ret 
    } // end of method Program::Main 
} // end of class Console1.Program 

再び組み立て微調整 は、ベースのオブジェクトを指すなし存在であります基本オブジェクトのメソッドテーブルには、Derivedクラスで定義されたメソッドm()のエントリがあります。

しかし、このコードは魔法のようにDerived.m()を実行します!!

だから、私は上記のコードでは理解していない二つの質問があります。

  1. は、以下のILコードで指定されたタイプの意義は何ですか?私はこれを異なるタイプ(例えばSystem.Exception !!)に変更して試してみましたが、エラーは報告されていません。なぜ??作品をcallvirtどのように正確

    .localsのinit([0]クラスConsole1.Base d)は

  2. ?コールはDerived.m()にどのようにルーティングされましたか?

ありがとうございます!

よろしく、 アジャイ

+0

を@ulrichb:私は彼がそれをすることができるとは思わない。彼は実際に(例外を投げる)キャストを使用しない以外は、 'Base b = new Base();((派生)b).mのようなものです。 – CodesInChaos

+2

コードは検証可能ですか? – CodesInChaos

+0

@ CodeInChaos:コードは検証できません! PEVerifyは "予期しないタイプのスタック"エラーを返します。 – ajay

答えて

5

私の推測では、ジッタはDerived.mが仮想ではないことを認識しているので、他の場所を指し示すことはできません。したがって、callvirtは、v-tableを介した呼び出しの代わりにヌルチェックと呼び出しに減少します。

Derived.mを仮想化しようとしてください。私はそれがスローされることを賭ける。

this!=nullを証明できない場合に非仮想メソッドを呼び出す場合でも、C#コンパイラはcallvirt命令を発行し、ヌルチェックを取得します。その場合、ジッタは、固定電話番号(またはインライン電話番号)で通常の通話によって仮想通話を置き換えるのに十分なインテリジェントです。

コードが検証可能かどうかを確認する必要があります。私はそうではないと思う。

1

デフォルトでは、ローカルマシンから実行されるコードが確認されていないことに注意してください。つまり、無効なコードを記述して実行することができます。私はあなたの主な機能はそのままではないと思う。 PEVerifyツールは、コードが型保証されていることを確認するためにアセンブリをチェックすることも、Security Policy Administrationによってローカルマシンまたは特定の場所からのコードに対してこれらのチェックを有効にすることもできます。

locals文の型の目的は、ローカル変数の型を宣言することです。これは、タイプ・ベリファイアがローカル変数上のメンバー・アクセスが正しいタイプのオブジェクト上で動作していることを検証するために必要な情報を提供します。

Callvirtはいくつかの方法で実装できます。最も可能性の高い方法は、C++のvtableが実装されているのと同じ方法です:オブジェクトには関数ポインタのテーブルが含まれています。各関数は、テーブル内の所定のオフセットに配置されています。関数を呼び出すには、あらかじめ定義されたオフセットのアドレスがロードされて呼び出されます。場合によっては、オブジェクトの型がわかっている場合、CLRは追加の最適化を実行できます。これが行われるかどうか、私は知らない。

1

これはJITコンパイラの最適化の副作用だと思います。 m()メソッドが仮想の場合、オブジェクトからメソッドテーブルポインタを掘り起こし、仮想呼び出しを行うためにマシンコードを生成する必要があります。しかし、このメソッドは仮想ではなく、JITコンパイラーはすでにDerivedクラスのメソッド・テーブル・ポインターを認識しています。したがって、ポインタの取得をバイパスして、直接それを供給します。あなたが観察した通りに通話をする。生成されたマシンコードをチェックすることで、私の推測を確認することができます。

ええと、IL検証者はここでポイントを獲得していません。 Derived.m()メソッドをDerivedで宣言されているフィールドで調整すると、より面白くなります。私はあまりにも多くのReflectionを見てきましたが、AccessViolationでコードクラッシュを起こして、これによって大きく驚かされました。しかし、それは意図的なことかもしれません、とにかくクラッシュするILを検証する必要はありません。確かに、これらの種類の検証の抜け穴を利用することは(まだ)共通していません。ありがたいことに。

+0

ありがとうハンス!私は、これがJITコンパイラによって行われた最適化の問題であるに違いないとも考えています。私はアセンブリ言語にはあまり慣れていないので、どうすればこのことを確認できるかわかりません。また、デバッガを接続すると、すべてのJIT最適化が無効になっていることを理解しています。 – ajay

+0

いいえ、これはデバッガでも起こりそうです。もちろん簡単にチェックできます。 –

2

あなたのコードは検証可能ではありません(peverifyまで実行してください)。 callvirtがどのように動作し、コードの実行方法を理解するのに役立つかについて、blog postと書いています。

CLRは、通常のプログラムとして実行すると、検証不可能なコードを実行しようとします。それが実際に問題を引き起こす場合にのみ、それはborkです。

例では、オブジェクトインスタンスの実際の実行時バイナリ表現が同じであるため、BaseのインスタンスでDerived.m()を呼び出すと機能します。 thisオブジェクトは基本的に同じであり、オブジェクトのインスタンスフィールドはアクセスされません。

このStackExchangeの質問/答えチェックアウト、これはボンネットの下にさらに深くどのように動作するかの詳細については、両方のメソッドにインスタンスフィールドへのアクセスを入れて試してみて、何が起こるか見て...

+0

ありがとうthecoop !!しかし、CodeInChaosが提案したようにjitコンパイラによる最適化のためにコードが実行されていると思われます – ajay

関連する問題