44

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

它简洁明了,并用逻辑右移代替了除以 2 的幂。

然而,当我编译这段代码时:

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

在进行右算术移位之前执行一些操作。

这些额外的操作需要什么?为什么算术移位还不够?

4

3 回答 3

91

原因是无符号除以 2^n 可以非常简单地实现,而有符号除法稍微复杂一些。

unsigned int u;
int v;

u / 4096等价于u >> 12的所有可能值u

v / 4096等价v >> 12- 它在 时分解v < 0,因为当涉及负数时,移位与除法的舍入方向不同。

于 2012-10-02T14:21:34.180 回答
34

“额外的操作”弥补了算术右移将结果向负无穷大舍入,而除法将结果向零舍入这一事实。

例如,-1 >> 1-1-1/2而是0

于 2012-10-02T14:23:12.193 回答
10

来自 C 标准:

整数相除时,/ 运算符的结果是去掉任何小数部分的代数商。105) 如果商 a/b 是可表示的,则表达式 (a/b)*b + a%b 应等于 a;否则,a/b 和 a%b 的行为都是未定义的。

不难想到 a 的负值不遵循纯算术移位的规则的例子。例如

(-8191) / 4096 -> -1
(-8191) % 4096 -> -4095

满足方程,而

(-8191) >> 12 -> -2 (assuming arithmetic shifting)

不是截断除法,因此-2 * 4096 - 4095肯定不等于 -8191。

请注意,负数的移位实际上是实现定义的,因此根据(-8191) >> 12标准,C 表达式没有通常正确的结果。

于 2012-10-02T14:30:22.950 回答