对于这样的代码:
int res = 0;
for (int i = 0; i < 32; i++)
{
res += 1 << i;
}
此代码已生成( Release模式,未附加调试器,64 位):
xor edx,edx
mov r8d,1
_loop:
lea ecx,[r8-1]
and ecx,1Fh ; why?
mov eax,1
shl eax,cl
add edx,eax
mov ecx,r8d
and ecx,1Fh ; why?
mov eax,1
shl eax,cl
add edx,eax
lea ecx,[r8+1]
and ecx,1Fh ; why?
mov eax,1
shl eax,cl
add edx,eax
lea ecx,[r8+2]
and ecx,1Fh ; why?
mov eax,1
shl eax,cl
add edx,eax
add r8d,4
cmp r8d,21h
jl _loop
现在我可以看到那里大多数指令的要点,但是 AND 指令怎么了?无论如何,ecx 永远不会在此代码中超过 0x1F,但我原谅它没有注意到这一点(也没有注意到结果是一个常量),它不是提前编译器毕竟可以花很多时间进行分析。但更重要的是,具有 32 位操作数的 SHL 已经通过 0x1F 屏蔽了 cl。所以在我看来,这些 AND 完全没有用。它们为什么会产生?他们有什么我想念的目的吗?
最佳答案
and
已经存在于 C# 编译器生成的 CIL 代码中:
IL_0009: ldc.i4.s 31
IL_000b: and
IL_000c: shl
CIL shl
指令的规范说:
The return value is unspecified if shiftAmount is greater than or equal to the size of value.
然而,C# 规范定义了 32 位移位以采用移位计数模 32:
When the type of x is
int
oruint,
the shift count is given by the low-order five bits of count. In other words, the shift count is computed fromcount & 0x1F
.
在这种情况下,C# 编译器除了发出显式 和
操作之外,真的做的再好不过了。最好的希望是 JITter 会注意到这一点并优化掉冗余的 和
,但这需要时间,而且 JIT 的速度非常重要。因此,请考虑为基于 JIT 的系统支付的价格。
我想,真正的问题是为什么 CIL 以这种方式指定 shl
指令,而 C# 和 x86 都指定了截断行为。我不知道,但我推测对于 CIL 规范来说,避免指定可能对某些指令集上昂贵的东西进行 JIT 的行为很重要。同时,对于 C# 来说,尽可能少的未定义行为很重要,因为人们总是会使用这些未定义的行为,直到下一个版本的编译器/框架/操作系统/无论什么改变它们,破坏代码。
关于c# - 为什么会产生AND指令?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9905668/