c++ 为什么将一个有符号整数除以2的幂会产生如此复杂的代码?

rdlzhqv9  于 2023-02-14  发布在  其他
关注(0)|答案(3)|浏览(200)

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

它干净简洁,用逻辑右移取代了二的幂除。
但是当我编译这段代码时:

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

在进行算术右移之前执行一些操作。
为什么需要这些额外的操作?为什么算术移位还不够?

esyap4oy

esyap4oy1#

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

unsigned int u;
int v;

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

cig3rfwq

cig3rfwq2#

“额外操作”补偿了算术右移将结果向负无穷舍入而除法将结果向零舍入的事实。
例如,-1 >> 1-1,而-1/20

jgwigjjp

jgwigjjp3#

根据C标准:
当整数被除时,/运算符的结果是去掉任何小数部分的代数商。如果商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按照标准通常没有正确的结果。

相关问题