为什么C#编译器会翻译这个!=就好像它是a>;比较?
我完全是偶然发现C#编译器将此方法转换为:为什么C#编译器会翻译这个!=就好像它是a>;比较?,c#,cil,il,notnull,binary-operators,C#,Cil,Il,Notnull,Binary Operators,我完全是偶然发现C#编译器将此方法转换为: static bool IsNotNull(object obj) { return obj != null; } ……在这方面: …或者,如果您更喜欢查看反编译的C代码: 那怎么会是=被翻译为“”?简短回答: IL中没有“比较不相等”指令,因此C#=运算符没有精确的对应关系,无法按字面翻译 但是,有一条“比较相等”指令(ceq,与=运算符直接对应),因此在一般情况下,x!=y被翻译成稍微长一点的等价物(x==y==false) IL(cgt
static bool IsNotNull(object obj)
{
return obj != null;
}
……在这方面:
…或者,如果您更喜欢查看反编译的C代码:
那怎么会是=代码>被翻译为“
”?简短回答:
IL中没有“比较不相等”指令,因此C#=代码>运算符没有精确的对应关系,无法按字面翻译
但是,有一条“比较相等”指令(ceq
,与=
运算符直接对应),因此在一般情况下,x!=y
被翻译成稍微长一点的等价物(x==y==false
)
IL(cgt
)中还有一条“compare Better Thor”(比较大于)指令,允许编译器采用某些快捷方式(即生成较短的IL代码),其中一条是将对象与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
}
事实证明,编译器并不总是生成这种相当冗长的模式。让我们看看用常量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
}
编译器可以利用有符号整数存储在中的事实(其中,如果生成的位模式被解释为无符号整数-这就是.un
的意思-0具有最小的可能值),因此它将x==0
转换为未选中((uint)x)>0
事实证明,编译器可以对null
执行同样的不等式检查:
static bool IsNotNull(object obj)
{
return obj != null;
}
编译器生成的IL与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
引用的位模式是任何对象引用可能的最小位模式
该快捷方式在(第491页,作为表6-4“二进制比较或分支操作”的脚注)中明确提到:
允许并可在ObjectRefs(O)上验证cgt.un。这通常用于将ObjectRef与null进行比较(没有“compare not equal”指令,否则这将是一个更明显的解决方案)
回答得很好,只有一个问题:二的补语在这里不相关。重要的是,有符号整数的存储方式应确保int
范围内的非负值在int
中的表示方式与在uint
中的表示方式相同。这是一个比补码弱得多的要求。无符号类型从来没有任何负数,因此比较为零的比较操作不能将任何非零的数字视为小于零。与int
的非负值相对应的所有表示都已被uint
中的相同值占用,因此与int
的负值相对应的所有表示都必须与uint
大于0x7fffff
的某个值相对应,但是它的值是多少并不重要。(实际上,真正需要做的就是在int
和uint
中用相同的方式表示零)@hvd:谢谢你的解释。你说得对,重要的不是两个人的互补;这是cgt.un
将int
视为uint
而不改变底层位模式的要求和事实。(假设cgt.un
首先尝试通过将所有负数映射到0来修复下溢。在这种情况下,显然无法用>0
替换!=0
)我发现使用
将对象引用与另一个对象引用进行比较是可验证的,这让我感到惊讶。这样就可以比较两个非空对象并得到一个布尔结果(这是非确定性的)。这不是内存安全问题,但感觉像是不干净的设计,不符合安全管理代码的一般精神。这种设计泄露了对象引用作为指针实现的事实。似乎是.NET CLI的设计缺陷。@usr:绝对是!本标准第III.1.1.4节指出“对象引用(O型)是完全不透明的”,并且“允许的唯一比较操作是相等和不相等……”可能是因为对象引用没有按照内存地址定义,所以本标准还注意在概念上将空引用与0分开(例如,参见ldnull
、initobj
和newobj
的定义)。因此使用cgt.un
比较对象引用与空引用在多个方面似乎与第III.1.1.4节相矛盾。
static bool IsNotNull(object obj)
{
return obj != null;
}
.method private hidebysig static bool IsNotNull(object obj) cil managed
{
ldarg.0
ldnull // (note: this is the only difference)
cgt.un
ret
}