为什么这样复杂的代码是用二的幂来分割一个有符号的整数?

当我用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/20

从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并没有像标准一样正确的结果。