右移和有符号整数

31

在我的编译器上,以下伪代码(值替换为二进制):

sint32 word = (10000000 00000000 00000000 00000000);
word >>= 16;

生成一个带有比特域的 word:

(11111111 11111111 10000000 00000000)

我可以在所有平台和C++编译器上依赖这种行为吗?

5个回答

32

以下是链接:INT34-C. 不要使用负数位数或大于等于操作数中存在的位数来移位表达式

不合规的代码示例(右移)

E1 >> E2 的结果是 E1 右移 E2 位。如果 E1 具有无符号类型或者 E1 具有带符号类型和非负值,则结果的值为 E1 / 2E2 的整数部分。如果 E1 具有带符号类型和负值,则得到的值是实现定义的,可能是算术(带符号)移位:

算术(带符号)移位

或逻辑(无符号)移位:

逻辑(无符号)移位

这个不合规的代码示例没有测试右操作数是否大于或等于推广的左操作数的宽度,因此导致未定义的行为。

unsigned int ui1;
unsigned int ui2;
unsigned int uresult;
 
/* Initialize ui1 and ui2 */
 
uresult = ui1 >> ui2;

假设一个右移运算是以算术(有符号)还是逻辑(无符号)方式实现的,可能会导致漏洞。请参见建议INT13-C. 仅对无符号操作数使用位运算符


2
有没有针对这个问题的实际建议?因为根据那条规则的名称,它并不适用于这里...你只是引用了一些提供的背景信息。 - Ben Voigt
第二个更相关的链接已经移动。新链接为https://wiki.sei.cmu.edu/confluence/display/c/INT13-C.+Use+bitwise+operators+only+on+unsigned+operands。 - underscore_d
2
如果您要移位的数量是编译时常量,您可以通过使用(有符号)除法来强制执行算术移位。因此,您可以写成'a / (1 << 16)'而不是'a >> 16',后者的实现是未定义的。编译器几乎肯定会将其替换为算术移位。 - Pablo Halpern

24

来自最新的C++20草案

有符号整数类型的右移是算术右移,它执行符号扩展。


2
等等!这是否意味着所有有符号值都需要使用二进制补码? - Adrian
4
@Adrian 是的 - user3624760
6
是的,C++20现在假设是2s补码。为什么?因为现在已经是2022年了,而且那些老旧硬件真的不重要了。全球每个可信赖的平台都支持2s补码,我们应该停止支持那些晦涩和古怪的硬件架构。 - Persixty
1
这是与现有架构调查章节相对应的论文: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r3.html#survey (主要是2s补码。最终措辞来自另一篇论文)。一些价值数百万美元的非常大型计算机采用1s补码,例如https://en.wikipedia.org/wiki/CDC_6600或https://en.wikipedia.org/wiki/UNIVAC_1100/2200_series。 - Sebastian
1
@alexpanter 原因有多方面:1. 大多数标准类型的运算符语义可以追溯到半个世纪前 -> 与现有的C代码兼容性。2. 目标架构之间的差异 -> 算法的可移植性,同时仍使用常见的本地ISA指令,这些指令在任何地方都可用。3. 例如,如果有符号值永不溢出,优化器可以推断出规则,比如“添加一个正数总是增加值” -> 性能优化。4. 还没有一个好的、令人信服的、全面的提案被提出 -> 改进最终有望到来。 - Sebastian
显示剩余4条评论

15

不可以依赖此行为。右移负数(我猜测您的示例处理的就是这种情况)是有实现定义的。


好的,这很公平。不过我仍然想知道,如果编译器创建了一个使用这种方法的二进制文件,它是否至少可以在大多数硬件上按预期工作? - Anne Quinn
5
如果你为某个特定架构编译了某些东西,那么它应该在该架构的所有实现中都能够正常工作。例如,x86对于符号扩展和非符号扩展移位有不同的操作,而编译器决定使用哪一个。这可能在其他架构上完全无法工作(指的是任何情况,而不仅仅是这种行为)。 - R. Martinho Fernandes

7

在C++中,不是的。这取决于实现和/或平台。

在其他一些语言中,是的。例如,在Java中,“>>”运算符被定义为始终使用最左侧的位进行填充(从而保留符号)。“>>>”运算符使用0进行填充。因此,如果您想要可靠的行为,则可能的一个选项是更改为另一种语言。(尽管显然,根据您的情况,这可能不是一个选项。)


3
据我所知,在C++中整数可以表示为带符号的,此时符号扩展将填充0。因此,您不能依赖此功能。

你是对的。标准确实提出了一些要求,使得二进制补码成为最优表示方式,但总的来说,有符号整数可以用任何实现想要的方式来表示。 - R. Martinho Fernandes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接