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

赫伯特·希尔特(Herbert Schildt)的书“C完整参考文献”说:“(在有符号的负整数的情况下,右移将导致1被引入,以便保留符号位。)”

保留标志位有什么意义?

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

Schildt书被广泛认为非常贫穷。

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

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

像Schildt先生的许多声明一样,声明是彻底和不准确的。 很多人建议扔掉他的书。 (在其他地方,请参阅Annotated Annotated C标准和ACCU评论 – 在Schildt上进行作者搜索;另请参阅Stack Overflow上的C书的最终列表 )。

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

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

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

关键是C >> (右移)运算符保留1 (符号) 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 – 这是实现定义的,但根据我的经验,大多数编译器选择算术(符号保留)移位指令。

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

不必要。 参见C标准C11 6.5.7:

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

这意味着编译器可以自由地移动它喜欢的任何东西(0或1),只要它记录它。