为什么对有符号整数进行二的幂次方除法时会生成如此复杂的代码?

45

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

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

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


3
就C89和C++03而言,针对负数操作数,整型除法向哪个方向舍入是由具体实现定义的。但在C99和C++11中不再是这样。 - Steve Jessop
1
可能是检查Visual Studio C++编译器生成的代码,第1部分的重复问题。 - fredoverflow
4
好的,我们也可以投票将旧问题作为新问题的重复关闭... - fredoverflow
2
这是一个很好的例子,当人们说不要用移位代替除法时,因为编译器知道优化。结果编译器也知道什么时候是“可以”的。 - phkahler
1
@AlexeyFrunze,咳咳,你还记得那天在代码编辑器中输入“Hello world”的时候吗?... - Alex B
显示剩余8条评论
3个回答

91

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

unsigned int u;
int v;

u / 4096 等同于 u >> 12,适用于所有可能的 u 值。

v / 4096 不等同于 v >> 12 - 当 v < 0 时会出现问题,因为当负数参与时,移位和除法的舍入方向是不同的。


@Vlad:我相信 int 默认情况下始终是有符号的 - 也许你想到的是 char - Paul R

35

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

例如,-1 >> 1 的结果是 -1,而 -1/2 的结果是 0


11

根据C标准:

  

当整数相除时,/运算符的结果是代数商,任何小数部分都被舍弃。    如果商a/b是可表示的,则表达式(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。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接