为什么这样复杂的代码是用二的幂来分割一个有符号的整数?
当我用VC ++ 10编译这个代码时:
DWORD ran = rand(); return ran / 4096;
我得到这个反汇编:
299: { 300: DWORD ran = rand(); 00403940 call dword ptr [__imp__rand (4050C0h)] 301: return ran / 4096; 00403946 shr eax,0Ch 302: } 00403949 ret
这是干净简洁的,用合理的右移代替了两位的权力。
然而,当我编译这个代码:
int ran = rand(); return ran / 4096;
我得到这个反汇编:
299: { 300: int ran = rand(); 00403940 call dword ptr [__imp__rand (4050C0h)] 301: return ran / 4096; 00403946 cdq 00403947 and edx,0FFFh 0040394D add eax,edx 0040394F sar eax,0Ch 302: } 00403952 ret
在进行正确的算术移位之前执行一些操作。
那些额外的操作需要什么? 为什么算术转换不够?
原因在于2 ^ n的无符号划分可以非常简单地实现,而有符号划分比较复杂。
unsigned int u; int v;
u / 4096
相当于u >> 12
,表示u
所有可能值。
v / 4096
不等于v >> 12
– 当v < 0
,它会发生故障,因为当涉及到负数时,舍入方向不同于移位与除法。
“额外的操作”补偿了这样的事实,即算术右移将结果向负无穷转,而分割将结果向零转。
例如, -1 >> 1
是-1
,而-1/2
是0
。
从C标准:
当整数被分解时,/运算符的结果是丢弃任何小数部分的代数商。如果商a / b是可表示的,则expression式(a / b)* b + a%b将等于a; 否则,a / b和a%b的行为都是未定义的。
我们不难想到,一个负值不符合这个规则的例子是纯粹的算术移位。 例如
(-8191) / 4096 -> -1 (-8191) % 4096 -> -4095
满足方程,而
(-8191) >> 12 -> -2 (assuming arithmetic shifting)
不是截断,所以-2 * 4096 - 4095
肯定不等于-8191。
请注意,负数的移位实际上是实现定义的,所以Cexpression式(-8191) >> 12
并没有像标准一样正确的结果。