有符号右移:哪些编译器使用逻辑移位?

15

我使用 Visual Studio、Ubuntu 的 GCC 编译器、英特尔编译器和 MinGW 进行了右移测试。所有的移位都在符号位上。我猜想 Xcode 的 GCC 也是这样做的。

我知道这种行为是实现特定的,但是看起来所有主流的桌面/服务器编译器都实现了算术移位。是否有任何广泛使用的编译器不在符号位上进行移位?

谢谢。


13
实现定义行为是由实现定义的。位移操作是把值看作一组位来处理。有符号数不应被视为位集合处理。使用无符号值。或者,如果想要除以二,则直接除以二 - 不要使用位移操作。如果这种优化是有效的,编译器会自动完成。另请参见https://www.securecoding.cert.org/confluence/display/cplusplus/INT13-CPP.+Use+bitwise+operators+only+on+unsigned+operands。 - Karl Knechtel
等等,你的意思是在C++中对无符号值进行右移操作是未定义的?!我不知道... - user541686
2
问题在于除以2和算术右移具有不同的行为。因此,如果使用除法,编译器无法将其更改为移位操作;如果想要带符号移位的行为,则在C语言中没有简单、可移植的表达方式。 - Chris Dodd
仅仅是对Karl评论的阐述,你可以在移位操作期间将有符号整数值视为无符号整数并获得可预测的行为...没有任何理由偏好实现特定的行为 - 即使你碰巧找到适合你需求的变体,它们随着新的编译器版本、编译器命令行选项、CPU型号等可能会消失。 - Tony Delroy
2
@Mehrdad:对于有符号值的右移是实现定义的...无符号值则没有问题。@pic11:顺便说一下-这不仅仅是符号位是否“移入”的问题,还涉及到新的最高有效位是0还是1(尽管当然,在实现定义的行为中,可能存在某些病态情况,其中结果是以上都不是 :-/)。 - Tony Delroy
5
最好不要知道也不在意。这样一来,您就不会因为疏忽而编写不可移植的代码。 - Jonathan Leffler
4个回答

18

C可以运行在许多不同的架构上。我是说很种不同的架构。你可以让C代码运行在嵌入式DSP和Cray超级计算机上。

大多数人认为的C标准中的“实现定义”部分只会在一些冷门的架构上出问题。例如,在某些DSP和Cray超级计算机上,CHAR_BIT可能非常巨大,如32或64。因此,如果您在x86上尝试您的代码并且可能慷慨地考虑PowerPC、ARM或SPARC,那么您不太可能遇到任何真正奇怪的情况。这很好。现在大部分代码都将在以字节为导向的架构上运行,具有二进制补码整数和算术移位。毫无疑问,在可预见的未来,任何新的CPU架构都将是这样的。

但让我们看看最常见的两种整数表示方法:二进制补码和反码:

switch ((-1) >> 1) {
case 0:
case -0:
    puts("Hello, ones' complement world!");
    // Possibly sign-magnitude.
    break;
case -1:
    puts("Hello, two's complement world!");
    break;
default:
    puts("Hello, computer without arithmetic shift");
    break;
}

别担心。当你想除以一个数时,只需使用/,需要进行位移操作时使用>>。即使是糟糕的编译器也擅长优化这些操作。(请记住,如果x为负数,则x/2 != x>>1,除非你在使用补码机器,但几乎肯定不是真的。)

标准确保如果 (int) x 不是负数,则 (int) x >> n == (unsigned) x >> n,因此编译器没有太多的余地做出完全意外的操作。


我尝试了你的代码。我运行的所有编译器都实现了算术移位。 - pic11
4
更换编译器不会有任何作用,这正是我要表达的观点。要想看到差异,你需要切换到一种补码架构。 - Dietrich Epp
5
如果愿意,DeathStation 9000编译器可以自由使用x86中的shr而不是sar。C标准只规定这是“实现定义”,并没有规定二进制补码系统必须使用算术移位。类似于您提到的关于良好CPU的观点,许多C假定实现选择是明智和有益的。仅仅遵循标准是*不足以让C编译器在现实生活中可用的。有时写完全可移植的代码并不值得麻烦或膨胀,因为ISO C在某些方面过于可移植。 - Peter Cordes

2

一般而言,这取决于编译器使用的目标架构。如果架构具有算术(有符号)和逻辑(无符号)移位指令,则该架构的C编译器将使用适当的指令。另一方面,如果只有逻辑移位指令,C编译器将仅使用该指令,即使它对负值不“做正确的事情”,因为C规范允许编译器执行任何操作。


2

Cray C编译器默认对有符号数进行逻辑右移,但也有选项可选择算术右移。

通常情况下,可以安全地假定有符号右移是算术右移。


1
如果您能提供有关移位行为的引用,那将更好。 - phuclv

-3
据我所知,>> 运算符执行的是算术位移。但是对于有符号和无符号整数,移位的执行方式是有区别的——对于有符号整数,会扩展 MSB(通常是一个符号位),而对于无符号整数,不会扩展 MSB(它们始终为非负数,因此符号位始终为零)。 编辑:将上述内容中的“通常”适用于所有情况。

2
C标准字面上说:“如果E1具有带符号类型和负值,则结果值是实现定义的”。因此,最好检查它在所有目标架构上的工作方式(但是是的,你很可能没问题)。 - rustyx

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