【问题标题】:Why is such complex code emitted for dividing a signed integer by a power of two?为什么会发出如此复杂的代码来将有符号整数除以 2 的幂?
【发布时间】:2012-09-23 09:56:14
【问题描述】:

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

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

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

【问题讨论】:

  • FWIW,在 C89 和 C++03 中,它是由实现定义的,负操作数的整数除法循环方式。在 C99 和 C++11 中不是。
  • @Mysticial 我认为每个称职的 C(++)/低级程序员要么已经知道它,要么能够弄明白。或者赞成票来自那些还不知道这一点的人。我可能错了。
  • @Mysticial 好吧,我们也可以投票结束旧问题作为新问题的欺骗......
  • 这是一个很好的例子,当你的意思是除法时人们说不要移位,因为编译器知道优化。结果编译器也知道什么时候“OK”。

标签: c++ visual-c++ assembly x86 division


【解决方案1】:

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

unsigned int u;
int v;

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

v / 4096 NOT 等同于 v >> 12 - 当 v < 0 时它会崩溃,因为当涉及负数时,移位和除法的舍入方向不同。

【讨论】:

  • @Vlad: 我相信int 默认情况下总是签名的——也许你在想char
【解决方案2】:

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

例如,-1 >> 1-1,而 -1/20

【讨论】:

    【解决方案3】:

    来自 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。

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

    【讨论】:

      猜你喜欢
      • 2017-02-03
      • 2021-03-14
      • 2018-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-11
      • 2016-05-05
      相关资源
      最近更新 更多