为什么C#编译器会将此变换!=比较,就像它是>比较一样?

sta*_*ica 147 c# il cil binary-operators notnull

我有机会发现C#编译器会改变这个方法:

static bool IsNotNull(object obj)
{
    return obj != null;
}
Run Code Online (Sandbox Code Playgroud)

...进入这个CIL:

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

...或者,如果您更喜欢查看反编译的C#代码:

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

怎么把它!=翻译成" >"?

sta*_*ica 201

简短回答:

IL中没有"比较不等于"指令,因此C#!=运算符没有确切的对应关系,也无法按字面翻译.

然而,存在"比较相等"指令(ceq==操作员的直接对应),因此在一般情况下,x != y被翻译为稍长的等价物(x == y) == false.

也是一个"比较,大于"在IL(指令cgt),它允许编译采取一定的快捷键(即产生较短的IL代码),一个是对空的对象,不平等的比较,obj != null,得到翻译,好像他们是" obj > null".


我们来详细介绍一下.

如果IL中没有"compare-not-equal"指令,那么编译器将如何翻译以下方法?

static bool IsNotEqual(int x, int y)
{
    return x != y;
}
Run Code Online (Sandbox Code Playgroud)

正如上面已经说了,编译器会转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
}
Run Code Online (Sandbox Code Playgroud)

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

static bool IsNotZero(int x)
{
    return x != 0;
}
Run Code Online (Sandbox Code Playgroud)

产生的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
}
Run Code Online (Sandbox Code Playgroud)

编译器可以利用有符号整数存储在二进制补码中的事实(其中,如果结果位模式被解释为无符号整数 - 这就是.un意味着 - 0具有最小可能值),因此它的翻译x == 0就好像它是unchecked((uint)x) > 0.

事实证明,编译器可以对不等式检查执行相同的操作null:

static bool IsNotNull(object obj)
{
    return obj != null;
}
Run Code Online (Sandbox Code Playgroud)

编译器生成的IL与以下几乎相同IsNotZero:

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

显然,允许编译器假设null引用的位模式是任何对象引用可能的最小位模式.

此快捷方式在公共语言基础结构注释标准(2003年10月第1版)中明确提及(第491页,作为表6-4"二进制比较或分支操作"的脚注):

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

  • 优秀的答案,只有一个:两个补码在这里不相关.唯一重要的是有符号整数的存储方式使得`int`范围内的非负值在`int`中具有与在`uint`中相同的表示.这比两个补充要求的要求要弱得多. (3认同)
  • 无符号类型从不具有任何负数,因此比较为零的比较操作不能将任何非零数字视为小于零.对应于`int`的非负值的所有表示已经被'uint`中的相同值占用,因此对应于`int`的负值的所有表示必须对应于`uint的*some*值. `大于'0x7FFFFFFF`,但是这个值并不重要.(实际上,所有真正需要的是在'int`和`uint`中以零表示相同的方式.) (3认同)
  • @hvd:谢谢你的解释.你是对的,重要的不是两个补充; 这是[你提到的]的要求(http://stackoverflow.com/questions/28781839/why-does-the-c-sharp-compiler-translate-this-comparison-as-if-it-were-a- com#comment45842111_28781840)*和*`cgt.un`将`int`视为`uint`而不改变基础位模式的事实.(想象一下,`cgt.un`首先尝试通过将所有负数映射到0来修复下溢.在这种情况下,你显然不能用`> 0`代替`!= 0`.) (3认同)
  • @usr:绝对![CLI标准](http://bit.ly/1IesnAK)的第III.1.1.4节说_"对象引用(类型O)是完全不透明的"_和那个_"唯一允许的比较操作是相等的不等式......"_也许因为对象引用不是根据内存地址定义的,所以标准也要注意从概念上将空引用与0分开(参见例如`ldnull`,`initobj`和`的定义. newobj`).因此,使用`cgt.un`将对象引用与空引用进行比较似乎与多个方面的第III.1.1.4节相矛盾. (3认同)
  • 我发现令人惊讶的是,使用`>`比较对象引用是另一个是可验证的IL.这样,可以比较两个非空对象并获得布尔结果(这是非确定性的).这不是一个内存安全问题,但感觉不洁净的设计不符合安全托管代码的一般精神.这种设计泄漏了对象引用实现为指针的事实.似乎是.NET CLI的设计缺陷. (2认同)