为什么在C中右移负数会使最左边的位数为1?

时间:2013-06-28 06:21:10

标签: c bit-manipulation bit-shift

Herbert Schildt撰写的“C完整参考文献”中写道“(在有符号的负整数的情况下,右移将导致1被引入,以便保留符号位。)”< / p>

保留符号位有什么意义?

此外,我认为这本书是指使用符号位而不使用two's complement来表示负数的情况。但即使在这种情况下,推理似乎也没有任何意义。

4 个答案:

答案 0 :(得分:14)

Schildt的书被广泛认为特别差。

事实上,当你右移一个带负号的数字时,C 保证1会被移入;右移右值的结果是实现定义的。

但是,如果负数的右移被定义为在1s内移位到最高位位置,那么在2s补码表示中它将表现为算术移位 - 右移N的结果与除以2 N 的结果相同,向负无穷大舍入。

答案 1 :(得分:7)

这一声明是彻底和不准确的,就像Schildt先生的许多声明一样。很多人建议扔掉他的书。 (在其他地方,请参阅The Annotated Annotated C StandardACCU Reviews - 在Schildt上进行作者搜索;另请参阅Stack Overflow上的Definitive List of C Books

实现定义是否正向移位负(必须符号)整数将零或1移位到高位。底层CPU(例如,ARM;另见class)通常有两个不同的底层指令 - ASR或算术右移和LSR或逻辑右移,其中ASR保留符号位和LSR才不是。允许编译器编写者选择其中之一,并且可能出于兼容性,速度或奇思妙想的原因这样做。

  

ISO / IEC 9899:2011§6.5.7按位移位运算符

     

¶5E1 >> E2的结果是E1右移E2位位置。如果E1具有无符号类型   或者如果E1具有签名类型和非负值,则结果的值为整数   部分E1 / 2 E2 的商。如果E1具有签名类型和负值,则   结果值是实现定义的。

答案 2 :(得分:1)

关键是C >>(右移)运算符保留 1 符号({signed} int

例如:

int main() {
  int a;
  unsigned int b;

  a = -8;
  printf("%d (0x%X) >> 1 = %d (0x%X)\n", a, a, a>>1, a>>1);

  b = 0xFFEEDDCC;
  printf("%d (0x%X) >> 1 = %d (0x%X)\n", b, b, b>>1, b>>1);

  return 0;
}

输出:

-8 (0xFFFFFFF8) >> 1 = -4 (0xFFFFFFFC)                    [sign preserved, LSB=1]
-1122868 (0xFFEEDDCC) >> 1 = 2146922214 (0x7FF76EE6)      [MSB = 0]

如果它没有保留标志,结果将完全没有意义。你会得到一个小的负数,并且通过向右移动一个(除以2),你最终会得到一个大的正数。

1 - 这是实现定义的,但根据我的经验,大多数编译器选择算术(符号保留)移位指令。

答案 3 :(得分:0)

  

在带符号的负整数的情况下,右移将导致1被引入,以便保留符号位

不一定。参见C标准C11 6.5.7:

  

E1&gt;的结果&gt; E2是E1右移E2位位置。如果E1有   无符号类型或E1具有带符号类型和非负值,   结果的值是E1 /的商的不可分割的一部分   2 E2 如果E1具有带符号类型和负值,则为结果值   是实现定义的。

这意味着编译器可以随心所欲地移动它(0或1),只要它能够记录它。