C 编译器可以实现有符号右移 "unreasonably"吗?

标签 c bit-manipulation language-lawyer bit-shift

标题字段不够长,无法捕获详细问题,因此为了记录,我的实际问题以特定方式定义“不合理”:

Is it legal1 for a C implementation have an arithmetic right shift operator that returns different results, over time, for the identical argument values? That is, must >> be a true function?

想象一下,您想在 C 中对有符号值使用右移 >>> 来编写可移植代码。不幸的是,对您来说,算法的某些关键部分的最有效实现在正确签名时是最快的移位填充符号位(即,它们是算术右移)。现在自 this behavior is implementation defined如果您想编写利用它的可移植代码,您就有点搞砸了。

如果您知道您将在 上运行的所有编译器,那么只需阅读编译器的文档(它需要在标准中提供,但实际上可能很容易访问或什至不存在)就很棒曾经运行过,但由于这通常是不可能的,您可能会寻找一种可移植的方法。

我想到的一种方法是在运行时简单地测试编译器的行为:它似乎实现算术2右移,使用优化算法,但如果不使用不依赖它的回退。当然,仅仅检查 (short)0xFF00 >> 4 == 0xFFF0 是不够的,因为它不排除 charint 值的工作方式不同,甚至是它填充某些偏移量或值但不填充其他值的奇怪情况3

因此,考虑到一个全面的方法是详尽地检查所有 类次值和金额。对于所有 8 位 char 输入,只有 28 个 LHS 值和 23 个 RHS 值,总共 211,而 short(通常情况下,如果你想学究气,我们可以说 int16_t)总计只有 220,这可以在在现代硬件上只需几分之一秒4。完成所有 237 32 位值在体面的硬件上需要几秒钟,但仍然可能是合理的。然而,在可预见的 future ,64 位会过时。

假设您这样做并发现 >> 行为完全匹配所需的算术移位行为。根据标准,您可以安全地依赖它吗:标准是否限制实现不在运行时更改其行为?也就是说,行为是否必须作为其输入的函数来表达,或者 (short)0xFF00 >> 4 是否可以先是 0xFFF0 然后再是 0x0FF0 (或任何其他值)下一个?

现在这个问题主要是理论上的,但考虑到混合架构(如 big.LITTLE)的存在,违反这一点可能并不像看起来那样疯狂动态地将进程从一个 CPU 移动到另一个可能存在细微的行为差异,或者在 Intel 和 AMD 制造的芯片之间进行实时 VM 迁移,具有细微的(通常未记录的)指令行为差异。


1 当然,“合法”是指根据 C 标准,而不是根据您所在国家/地区的法律

2 即,它复制新移位的符号位。

3 这有点疯狂,但不是疯狂:对于 1 的移位而不是其他移位,x86 具有不同的行为(溢出标志位设置),并且ARM 屏蔽了 surprising way 中的移位量一个简单的测试可能检测不到。

4 至少比微 Controller 更好。内部验证循环是一些简单的指令,因此 1 GHz CPU 可以在约 1 毫秒内以每个周期一条指令验证所有约 100 万个短值。

最佳答案

Let's say you did that and found that the >> behavior exactly matches the desired arithmetic shift behavior. Are you safe to rely on it according to the standard: does the standard constrain the implementation not to change its behavior at runtime? That is, does the behavior have to be expressible as a function of it's inputs, or can (short)0xFF00 >> 4 be 0xFFF0 one moment and then 0x0FF0 (or any other value) the next?

该标准没有对右移负整数行为的(必需)定义的形式或性质提出任何要求。特别是,它不禁止定义以操作数以外的编译时或运行时属性为条件。实际上,一般情况下实现定义的行为就是这种情况。标准defines the term就像

unspecified behavior where each implementation documents how the choice is made.

因此,例如,一个实现可能会提供一个宏、一个全局变量或一个函数,通过它可以在算术移位和逻辑移位之间进行选择。实现也可以定义右移一个负数来做不太合理的事情,甚至是疯狂的im合理的事情。

测试行为是一种合理的方法,但它只能为您提供概率答案。然而,在实践中,我认为对每种感兴趣的 LHS 数据类型执行少量测试(可能少到一个)是非常安全的。您极不可能遇到实现标准算术(始终)或逻辑(始终)右移以外的任何实现的实现,或者您会遇到算术和逻辑移位之间的选择因不同操作数而异的实现相同的(提升的)类型。

关于C 编译器可以实现有符号右移 "unreasonably"吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42166720/

相关文章:

将 ull 转换为 mpz_t

c - 切换数据类型 int 的第 100 位会产生意想不到的结果

c++ - mpi 环境中 C/C++ 并行分布式 Cholesky 分解的库?

javascript - 表示数字的位数

java - 在 Java 中使用位操作的集合的所有可能子集

c++ - 检测继承的函数是否被覆盖

c - (*)[] 和 *[] 声明之间的区别

java - 哪个代码在时间复杂度方面表现更好?

c++标准部分ID,其中提到析构函数隐含地没有抛出

c++ - GCC 对可能有效的代码抛出 init-list-lifetime 警告?