对带符号整数使用按位补码(一元~
)运算符具有实现定义和未定义的方面。换句话说,即使只考虑二进制补码实现,这段代码也不具备可移植性。
需要注意的是,即使在C语言中使用二进制补码表示法也可能有突陷表示法。
6.2.6.2p2甚至明确说明了这一点:
如果符号位为1,则value应以以下方式修改:
-- 对应的带有符号位0的值被否定(符号和大小);
-- 符号位的值为 -(2 M ) (二进制补码);
-- 符号位的值为 -(2 M - 1) (补数).
实现定义哪个适用于这些情况,是否将符号位为1且所有值位为0(对于前两种情况)或符号位和所有值位为1(对于补数)视为陷阱表示法或正常值也是如此。
强调是我的。 使用陷阱表示法是未定义行为。
在默认模式下,有一些实际的实现将该值保留为陷阱表示。我通常引用的一个著名的实现是
Unisys Clearpath Dordado on OS2200 (go to 2-29)。请注意该文档的日期;这样的实现并不一定古老(这也是我引用此文档的原因)。
根据
6.2.6.2p4,将负值向左移动是未定义的行为。我没有对实际存在的行为进行过多的研究,但我合理地期望可能会有一些实现进行符号扩展,也可能有一些实现不这样做。这也是形成上述提到的“陷阱表示”的一种方式,其性质是未定义的,因此不可取。从理论上讲(或者在遥远或不那么遥远的将来),您可能还会面临“对应于计算异常的信号”(这是类似于
SIGSEGV
的C标准类别,对应于诸如“除以零”之类的事情)或其他不稳定和/或不可取的行为...
总之,这个问题中的代码能够工作的唯一原因是你实现所做的决策恰好对齐。如果你使用我列出的实现方法,你可能会发现这段代码对于某些值并没有按照预期工作。
如评论中所描述的那样,这种
重型巫术并不是必需的,也不是我认为看起来最优的解决方案。如果你想要一个不依赖
魔法(例如,可移植的)来解决这个问题的方案,请考虑使用这个(实际上,这段代码至少可以处理
1 <= n <= 64
):
#include <stdint.h>
int fits_bits(intmax_t x, unsigned int n) {
uintmax_t min = 1ULL << (n - 1),
max = min - 1;
return (x < 0) * min + x <= max;
}