Herbert Schildt 的《C The Complete Reference》一书说:“(在有符号负整数的情况下,右移将导致带入 1,从而保留符号位。)”
保留符号位有什么意义?
此外,我认为这本书指的是使用符号位而不是使用二进制补码表示负数的情况。但即使在那种情况下,推理似乎也没有任何意义。
Herbert Schildt 的《C The Complete Reference》一书说:“(在有符号负整数的情况下,右移将导致带入 1,从而保留符号位。)”
保留符号位有什么意义?
此外,我认为这本书指的是使用符号位而不是使用二进制补码表示负数的情况。但即使在那种情况下,推理似乎也没有任何意义。
席尔特的书被广泛认为是非常糟糕的。
事实上,C不保证当您右移一个负符号数时会移入 1。右移负值的结果是实现定义的。
但是,如果将负数的右移定义为将 1 移到最高位位置,那么在 2 的补码表示中,它将表现为算术移位- 右移 N 的结果将与除法相同乘以 2 N,向负无穷大舍入。
与 Schildt 先生的许多声明一样,该声明是全面且不准确的。许多人建议把他的书扔掉。(在其他地方,请参阅The Annotated Annotated C Standard和ACCU 评论——在 Schildt 上进行作者搜索;另请参阅Stack Overflow 上C 书籍的权威列表)。
它是由实现定义的,是否右移一个负(必须有符号)整数将零或一移到高位。底层 CPU(例如,ARM;另请参见此类)通常有两个不同的底层指令——ASR 或算术右移和 LSR 或逻辑右移,其中 ASR 保留符号位而 LSR 不保留。编译器编写者可以选择其中之一,并且出于兼容性、速度或奇思妙想的原因可能会这样做。
ISO/IEC 9899:2011 §6.5.7 位移位运算符
¶5 的结果
E1 >> E2
是E1
右移的E2
位位置。如果E1
具有无符号类型或E1
具有带符号类型和非负值,则结果的值是 E1 / 2 E2商的整数部分。如果E1
具有带符号类型和负值,则结果值是实现定义的。
关键是 C >>
(右移)运算符保留1的符号 a (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]
如果它不保留符号,结果将完全没有意义。你会取一个小的负数,然后右移一个(除以二),你最终会得到一个大的正数。
1 - 这是实现定义的,但根据我的经验,大多数编译器选择算术(保留符号)移位指令。
在有符号负整数的情况下,右移将导致带入 1,以便保留符号位
不必要。参见 C 标准 C11 6.5.7:
E1 >> E2 的结果是 E1 右移 E2 位位置。如果 E1 具有无符号类型或 E1 具有有符号类型和非负值,则结果的值是 E1 / 2 E2的商的整数部分。如果 E1 具有带符号类型和负值,则结果值是实现定义的。
这意味着编译器可以自由地转换它喜欢的任何值(0 或 1),只要它记录它。