为什么 C# 编译器将此 != 比较翻译成 > 比较?
Why does the C# compiler translate this != comparison as if it were a > comparison?
我偶然发现 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)
}
为什么 !=
会被翻译成“>
”?
简答:
IL中没有“compare-not-equal”指令,所以C#中的!=
运算符没有确切的对应关系,无法直译
但是有一个“比较等于”指令(ceq
,直接对应于 ==
运算符),所以在一般情况下,x != y
被翻译成它的稍长一点相当于 (x == y) == false
.
在 IL (cgt
) 中还有 一条“比较大于”指令,它允许编译器采取某些快捷方式(即生成更短的 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
}
编译器可以利用有符号整数存储在 two's complement 中这一事实(其中,如果生成的位模式被解释为无符号整数——这就是 .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
引用的位模式是任何对象引用可能的最小位模式。
此快捷方式在 Common Language Infrastructure Annotated Standard (1st edition from Oct 2003)(第 491 页,作为 Table 6-4,“二进制比较或分支操作”的脚注)中明确提及:
"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)."
我偶然发现 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)
}
为什么 !=
会被翻译成“>
”?
简答:
IL中没有“compare-not-equal”指令,所以C#中的!=
运算符没有确切的对应关系,无法直译
但是有一个“比较等于”指令(ceq
,直接对应于 ==
运算符),所以在一般情况下,x != y
被翻译成它的稍长一点相当于 (x == y) == false
.
在 IL (cgt
) 中还有 一条“比较大于”指令,它允许编译器采取某些快捷方式(即生成更短的 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
}
编译器可以利用有符号整数存储在 two's complement 中这一事实(其中,如果生成的位模式被解释为无符号整数——这就是 .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
引用的位模式是任何对象引用可能的最小位模式。
此快捷方式在 Common Language Infrastructure Annotated Standard (1st edition from Oct 2003)(第 491 页,作为 Table 6-4,“二进制比较或分支操作”的脚注)中明确提及:
"
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)."