C#コンパイラがこの!=比較を>比較のように変換するのはなぜですか?


147

たまたま、C#コンパイラがこのメソッドを有効にすることを発見しました。

static bool IsNotNull(object obj)
{
    return obj != null;
}

…このCILに

.method private hidebysig static bool IsNotNull(object obj) cil managed
{
    ldarg.0   // obj
    ldnull
    cgt.un
    ret
}

…または、逆コンパイルされたC#コードを確認する場合:

static bool IsNotNull(object obj)
{
    return obj > null;   // (note: this is not a valid C# expression)
}

どのようにすることを来る!=」と訳されますか>「?

回答:


201

短い答え:

ILには「compare-not-equal」命令!=がないため、C#演算子には正確な対応がなく、文字どおりに変換できません。

ただし、「等しい」命令(ceq==演算子への直接の対応)があるため、一般的にx != yは、少し長い同等のように変換され(x == y) == falseます。

あり「比較-大なり」IL(の命令cgt)コンパイラはヌルに対するオブジェクトの不平等の比較である、(すなわち短いILコードを生成する)1を特定のショートカットを取ることができ、obj != null彼らがいたかのように、「変換されますobj > null

もう少し詳しく見てみましょう。

ILに「compare-not-equal」命令がない場合、コンパイラは次のメソッドをどのように変換しますか?

static bool IsNotEqual(int x, int y)
{
    return x != y;
}

上ですでに述べたように、コンパイラーはをにx != y変え(x == y) == falseます:

.method private hidebysig static bool IsNotEqual(int32 x, int32 y) cil managed 
{
    ldarg.0   // x
    ldarg.1   // y
    ceq
    ldc.i4.0  // false
    ceq       // (note: two comparisons in total)
    ret
}

コンパイラは、このかなり長い時間のパターンを常に生成するとは限らないことがわかりました。y定数0に置き換えるとどうなるか見てみましょう。

static bool IsNotZero(int x)
{
    return x != 0;
}

生成されるILは、一般的な場合よりもやや短くなります。

.method private hidebysig static bool IsNotZero(int32 x) cil managed 
{
    ldarg.0    // x
    ldc.i4.0   // 0
    cgt.un     // (note: just one comparison)
    ret
}

コンパイラは、符号付き整数の中に格納されているという事実を利用することができ、2の補数( -何結果のビットパターンは符号なし整数として解釈されている場合、.unそれは変換して、0可能な最小値を有する-手段)はx == 0、それがあたかもunchecked((uint)x) > 0

コンパイラーが不等式チェックに対しても同じようにできることがわかりましたnull

static bool IsNotNull(object obj)
{
    return obj != null;
}

コンパイラーは、とほぼ同じILを生成しIsNotZeroます。

.method private hidebysig static bool IsNotNull(object obj) cil managed 
{
    ldarg.0
    ldnull   // (note: this is the only difference)
    cgt.un
    ret
}

どうやら、コンパイラーは、null参照のビットパターンがオブジェクト参照で可能な最小のビットパターンであると想定することを許可されています。

このショートカットは、共通言語インフラストラクチャアノテーション標準(2003年10月の第1版)(491ページで、表6-4「バイナリ比較またはブランチ演算」の脚注として明示的に言及されています。

" cgt.unはObjectRefs(O)で許可および検証可能です。これは、ObjectRefをnullと比較するときに一般的に使用されます(" compare-not-equal "命令はありません。


3
すばらしい答え、たった1ニット:2の補数はここでは関係ありません。符号付き整数が格納されるのintは、の範囲内の負でない値がと同じように表現さintれることだけですuint。これは、2の補数よりもはるかに弱い要件です。

3
符号なしの型には負の数が含まれないため、ゼロと比較する比較演算では、ゼロ以外の数値をゼロ未満として処理できません。の非負の値に対応するすべての表現はint、の同じ値ですでに使用されているuintため、負の値に対応するすべての表現は、より大きい値intに対応する必要がありますが、実際にどの値でもかまいません。です。(実際には、本当に必要だとすべてがゼロの両方で同じように表現されていることであるとする。)uint0x7FFFFFFFintuint

3
@hvd:説明してくれてありがとう。あなたの言うとおり、重要なのは2の補数ではありません。必要条件である、あなたが言及したこと 、実際そのcgt.un扱いintとしてuint基本となるビットパターンを変更せずに。(cgt.un最初にすべての負の数値を0にマッピングしてアンダーフローを修正しようとすることを想像してください。その場合、明らか> 0!= 0。の代わりにできません。)
stakx-15年

2
オブジェクト参照を使用してオブジェクト参照を別のオブジェクト参照と比較すること>が検証可能なILであることは驚くべきことです。そうすれば、2つのnull以外のオブジェクトを比較して、ブール値の結果(非決定的)を取得できます。これはメモリの安全性の問題ではありませんが、安全なマネージコードの一般的な精神にはない、不潔なデザインのように感じられます。この設計は、オブジェクト参照がポインターとして実装されるという事実を漏らします。.NET CLIの設計上の欠陥のようです。
USR

3
@usr:絶対に!セクションIII.1.1.4 CLI標準はと言う「オブジェクト参照(O型)が完全に不透明である」とその「操作は許可さだけで比較が平等と不平等です...。」オブジェクト参照がされているので、おそらくいないメモリアドレスで定義、標準はまた、(例えばの定義を参照してください概念的に0から離れヌル参照を保持するために世話をするldnullinitobjnewobj)。したがって、cgt.unオブジェクト参照をnull参照と比較するためのの使用は、セクションIII.1.1.4と複数の点で矛盾しているように見えます。
stakx-2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.