为什么 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)
}

为什么!=被翻译成">"?

简短回答:

不存在";比较不相等";指令,因此C#!=运算符没有精确的对应关系,无法从字面上翻译。

然而,存在一个";比较相等";指令(ceq,与==运算符的直接对应关系),因此在一般情况下,x != y会像其稍长的等价(x == y) == false一样被翻译。

还有一个";比较大于"0";IL(cgt)中的指令,它允许编译器采用某些快捷方式(即生成较短的IL代码),其中之一是对象与null(obj != null)的不等式比较被转换为"零";CCD_ 10";。

让我们更详细地介绍一下

如果不存在";比较不相等";指令,那么编译器将如何翻译以下方法?

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
}

事实证明,编译器并不总是产生这种冗长的模式。让我们看看当我们用常数0:替换y时会发生什么

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;
}

编译器生成与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引用的位模式是任何对象引用可能的最小位模式。

通用语言基础设施注释标准(2003年10月第1版)(第491页,作为表6-4"二进制比较或分支运算"的脚注)中明确提到了这种快捷方式:

";在ObjectRefs(O)上允许并可验证cgt.un。这通常用于将ObjectRef与null进行比较(没有"compare not equal"指令,否则这将是一个更明显的解决方案)"

相关内容

  • 没有找到相关文章

最新更新