Why does the C# compiler translate this != comparison as if it were a > comparison?

本文深入解析了C#表达式在中间语言(IL)代码中的翻译过程,特别是!=运算符如何被转化为>操作符。通过具体实例展示了在不同场景下,编译器如何优化和简化代码实现不等于比较。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Question:

I have by pure chance discovered that the C# compiler turns this method:

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

…into this IL:

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

…or, if you prefer looking at decompiled C# code:

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

How come that the != gets translated as a ">"?


Answer:

Short answer:

There is no "compare-not-equal" instruction in IL, so the C# != operator has no exact correspondence and cannot be translated literally.

There is however a "compare-equal" instruction (ceq, a direct correspondence to the == operator), so in the general case, x != y gets translated like its slightly longer equivalent (x == y) == false.

There is also a "compare-greater-than" instruction in IL (cgt) which allows the compiler to take certain shortcuts (i.e. generate shorter IL code), one being that inequality comparisons of objects against null, obj != null, get translated as if they were "obj > null".


Let's go into some more detail.

If there is no "compare-not-equal" instruction in IL, then how will the following method get translated by the compiler?

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

As already said above, the compiler will turn the x != y into (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
}

It turns out that the compiler does not always produce this fairly long-winded pattern. Let's see what happens when we replace y with the constant 0:

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

The IL produced is somewhat shorter than in the general case:

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

The compiler can take advantage of the fact that signed integers are stored in two's complement(where, if the resulting bit patterns are interpreted as unsigned integers — that's what the .un means — 0 has the smallest possible value), so it translates x == 0 as if it were unchecked((uint)x) > 0.

It turns out the compiler can do just the same for inequality checks against null:

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

The compiler produces almost the same IL as for IsNotZero:

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

Apparently, the compiler is allowed to assume that the bit pattern of the null reference is the smallest bit pattern possible for any object reference.

This shortcut is explicitly mentioned in the Common Language Infrastructure Annotated Standard (1st edition from Oct 2003) (on page 491, as a footnote of Table 6-4, "Binary Comparisons or Branch Operations"):

"cgt.un is allowed and verifiable on ObjectRefs (O). This is commonly used when comparing an ObjectRef with null (there is no "compare-not-equal" instruction, which would otherwise be a more obvious solution)."



转载于:https://www.cnblogs.com/vigorz/p/10499219.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值