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

sha*_*oth 44 c++ x86 assembly division visual-c++

当我用VC++ 10编译这段代码时:

DWORD ran = rand();
return ran / 4096;
Run Code Online (Sandbox Code Playgroud)

我得到这个反汇编:

299: {
300:    DWORD ran = rand();
  00403940  call        dword ptr [__imp__rand (4050C0h)]  
301:    return ran / 4096;
  00403946  shr         eax,0Ch  
302: }
  00403949  ret
Run Code Online (Sandbox Code Playgroud)

这是一个干净,简洁,用一个逻辑右移的2的幂代替一个除法.

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

int ran = rand();
return ran / 4096;
Run Code Online (Sandbox Code Playgroud)

我得到这个反汇编:

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
Run Code Online (Sandbox Code Playgroud)

在进行正确的算术移位之前执行一些操作.

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

Pau*_*l R 90

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

unsigned int u;
int v;
Run Code Online (Sandbox Code Playgroud)

u / 4096相当于u >> 12所有可能的值u.

v / 4096NOT相当于v >> 12-它发生故障时v < 0,由于舍入方向是用于移位与除法时负数涉及不同.


Ste*_*non 34

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

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


pmd*_*mdj 10

从C标准:

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

不难想象a的负值不遵循此规则的纯算术移位.例如

(-8191) / 4096 -> -1
(-8191) % 4096 -> -4095
Run Code Online (Sandbox Code Playgroud)

满足等式,而

(-8191) >> 12 -> -2 (assuming arithmetic shifting)
Run Code Online (Sandbox Code Playgroud)

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

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