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



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

C实现具有算术权限合法吗shift运算符,随着时间的推移,为相同的参数值?也就是说,>>必须是真函数吗?

想象一下,您想在C中的有符号值上使用右移>>来编写可移植代码。不幸的是,对您来说,当有符号右移填充符号位时,算法某些关键部分的最有效实现是最快的(即,它们是算术右移)。现在,由于这种行为是由实现定义的,如果你想编写利用它的可移植代码,你就有点完蛋了

如果您知道将在上运行并且将在上一直运行的所有编译器,那么只需阅读编译器的文档(标准中要求它提供这些文档,但实际上可能很容易访问,也可能不容易存在)是非常好的,但由于这通常是不可能的,您可能会寻找一种方便的方法来实现这一点。

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

因此,考虑到一种全面的方法是彻底检查所有偏移值和量。对于所有8位char输入,只有28LHS值和23RHS值,总共211,而short(通常,但如果你想变得迂腐,就说int16_t)总共只有220。这可以在现代硬件4上在几分之一秒内得到验证。在不错的硬件上,完成所有23732位值需要几秒钟的时间,但仍然可能是合理的。然而,在可预见的未来,64位已经过时。

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

现在这个问题大多是理论性的,但考虑到big等混合架构的存在,违反这一点可能并不像看起来那么疯狂。动态地将进程从一个CPU移动到另一个CPU,可能存在微小的行为差异,或者在英特尔和AMD制造的芯片之间进行实时虚拟机迁移,这些芯片具有细微的(通常未记录的)指令行为差异。


1我所说的"合法"是指根据C标准的,而不是根据贵国法律

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

3这有点疯狂,但这并不是疯狂:x86对1的移位有不同的行为(溢出标志位设置),而不是其他移位,ARM以一种简单测试可能无法检测到的惊人方式屏蔽了移位量。

4至少有比微控制器更好的东西。内部验证循环是几个简单的指令,因此1GHz的CPU可以在每个周期一条指令的情况下,在约1ms内验证所有约100万个短值。

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

该标准没有对右移负整数行为的(必需的)定义的形式或性质提出任何要求。特别是,它并不禁止定义以操作数之外的编译时或运行时属性为条件。事实上,实现定义的行为通常就是这样。该标准将术语简单定义为

未指定的行为,其中每个实现都记录了如何进行选择。

因此,例如,一个实现可能提供一个宏、一个全局变量或一个函数,用于在算术和逻辑移位之间进行选择。实现还可以定义右移负数,以做不太可信的事情,甚至疯狂地im可信的事情。

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

标准的作者没有努力想象和禁止实现的每一种不合理的行为方式。在标准之前的日子里,实现几乎总是根据底层平台的工作方式来定义许多行为,而标准的作者几乎可以肯定地预计,大多数实现也会这样做。然而,由于该标准的作者不想排除实现以其他方式(例如支持针对其他平台的代码)有时可能有用的可能性,因此他们对许多事情都持开放态度。

对于"实施定义"行为需要记录的精确程度,该标准有些模糊。我认为这样做的目的是,一个有合理智慧的人阅读规范应该能够预测一段具有实现定义行为的代码的行为,但我不确定将操作定义为"产生寄存器7中发生的任何内容"是否不符合要求。

从实践的角度来看,重要的是,一个人是针对过去25年内开发的平台的高质量实现,还是试图迫使故意迟钝的编译器以可控的方式运行。如果是前者,它可能是值得的使用静态断言来确保-1>>1==-1,但现代编译器的质量测试通过的硬件将使用算术右移一贯地而针对迟钝编译器的代码可能出于某种目的,通常不可能防范一个病态但顺从的编译器可能会破坏一个人的代码和工作尝试这样做的花费通常可以更有效地用于获得高质量编译器。

最新更新