web-dev-qa-db-ja.com

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

私は、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)
}

どうして!=は「> "?

147
stakx

短い答え:

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

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

alsoIL(cgt)には「compare-greater-than」命令があり、コンパイラが特定のショートカット(つまり短いILコードを生成します)。1つは、オブジェクトとnull 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 == 0unchecked((uint)x) > 0であるかのように変換します。

コンパイラーは、nullに対する不等式チェックでも同じことができることがわかります。

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

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

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

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

このショートカットは、 Common Language Infrastructure Annotated Standard(2003年10月からの第1版) (491ページ、表6-4「バイナリ比較またはブランチ操作」の脚注として)に明示的に記載されています。

"cgt.unはObjectRef(O)で許可および検証可能です。これは、ObjectRefとnullを比較するときによく使用されます(そうでない場合、「compare-not-equal」命令はありません。明らかな解決策)。 "

201
stakx