为什么发出这样复杂的代码来将有符号整数除以2的幂?

时间:2012-10-02 14:20:26

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

当我使用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 个答案:

答案 0 :(得分:90)

原因是2 ^ n的无符号除法可以非常简单地实现,而有符号除法则更复杂。

unsigned int u;
int v;
对于u / 4096的所有可能值,

u >> 12相当于u

v / 4096 NOT 等同于v >> 12 - 它在v < 0时分解,因为当涉及负数时,转移与分割的舍入方向不同

答案 1 :(得分:34)

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

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

答案 2 :(得分:10)

来自C标准:

  

当整数被分割时,/运算符的结果是   代数商与任何小数部分丢弃.105)如果   商a / b是可表示的,表达式(a / b)* b + a%b应为   等于否则,a / b和%b的行为都是未定义的。

不难想象a的负值不遵循此规则的纯算术移位。 E.g。

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

满足等式,而

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

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

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