int a = 1 << 32;
int b = 1 << 31 << 1;
为什么a == 1
呢?b
正如我所料,是 0。
对于整数,所有班次都是 mod 32,对于 long 是 mod 64。
如果左侧操作数的提升类型是
int
,则仅将右侧操作数的五个最低位用作移位距离。就好像右手操作数经过位逻辑与运算符 & (§15.22.1) 与掩码值 0x1f。因此,实际使用的移位距离始终在 0 到 31 的范围内,包括 0 到 31。如果左侧操作数的提升类型是
long
,则仅将右侧操作数的最低六位用作移位距离。就好像右手操作数经过位逻辑与运算符 & (§15.22.1) 与掩码值 0x3f。因此,实际使用的移位距离总是在 0 到 63 的范围内,包括 0 到 63。
至于为什么语言是这样设计的——我不知道,但 C# 有相同的设计决定。这是带注释的 ECMA C# 规范所说的:
C# 故意将实现定义的行为保持在最低限度。仅当强制统一行为的性能影响过大(例如某些浮点精度问题)时,才接受它们。因此,每个整数类型的大小都被精确指定,并且字符集被固定为 Unicode。
对于移位操作,也指定了统一的行为。它可以使用一条额外指令(& 0x1F 或 & 0x3F)来实现,这在现代处理器上只产生很小的成本,特别是因为它不引用内存。与浮点运算不同,如果任由处理器随心所欲,移位行为的差异将是巨大的。将产生完全不同的积分结果,而不是精度上的微小差异。
在做出这个决定时,委员会研究了许多不同处理器架构的参考资料。对于 32 位操作数,超出范围 -32..+32 和 64 位操作数分别为 -64..+64 的移位计数的行为几乎没有一致性。
(然后是一些示例的列表。)
这对我来说似乎是一个完全合理的解释。一致性绝对重要,如果无法在某些系统上以高性能的方式实现不同的一致性行为,我认为这是一个合理的解决方案。
处理器实现移位指令的方式存在一些差异。
例如,IIRC、ARM 处理器(32 位 ISA)占用移位寄存器的最低有效字节。(移位实际上不是 ARM 上的独立指令)。
只要底层处理器有一种模糊合理的移位方式,清除除最低有效位之外的所有位(通常是一条指令)比检查移位是否大和分支(实际上在 ARM 上通常只添加一条指令)更容易因为所有指令都是有条件的)。