此问题的灵感来自StackOverflow的其他问题。今天,在浏览StackOverflow时,我遇到了一个将变量按位k移位的问题,该值k> =该变量的宽度(以位为单位)。这意味着将32位int移位32位或更多位。
Left shift an integer by 32 bits
Unexpected C/C++ bitwise shift operators outcome
从这些问题来看,很明显,如果我们尝试将数字移位k位,即> =变量的位宽,则仅采用最低有效的log2k位。对于32位int,最低有效5位被屏蔽并作为移位量。
因此,通常,如果w =变量的宽度(以位为单位),
x >> k
成为x >> (k % w)
对于int
,这是x >> (k % 32)
。
计数被掩码为5位,这将计数范围限制为0到31。
所以我写了一个小程序来观察理论上应该产生的行为。我已经在注释中写出了所产生的偏移量%32。
#include <stdio.h>
#include <stdlib.h>
#define PRINT_INT_HEX(x) printf("%s\t%#.8x\n", #x, x);
int main(void)
{
printf("==============================\n");
printf("Testing x << k, x >> k, where k >= w\n");
int lval = 0xFEDCBA98 << 32;
//int lval = 0xFEDCBA98 << 0;
int aval = 0xFEDCBA89 >> 36;
//int aval = 0xFEDCBA89 >> 4;
unsigned uval = 0xFEDCBA89 >> 40;
//unsigned uval = 0xFEDCBA89 >> 8;
PRINT_INT_HEX(lval)
PRINT_INT_HEX(aval)
PRINT_INT_HEX(uval)
putchar('\n');
return EXIT_SUCCESS;
}
输出与移位指令的预期行为不匹配!
==============================
Testing x << k, x >> k, where k >= w
lval 00000000
aval 00000000
uval 00000000
================================================ =====================
实际上,我对Java有点困惑。在C / C ++中,将int移位比位宽大一些的位可能会减少k%w,但是C标准不能保证。没有规则说这种行为应该一直发生。这是未定义的行为。
但是,在Java中就是这种情况。这是Java编程语言的规则。
Bitshift operators description in Java language specification
答案 0 :(得分:2)
链接的问题特别指出,移位量大于要移位的类型的位宽,则调用undefined behavior,标准将其定义为“使用非便携式或错误程序构造或错误行为”数据,本国际标准对此不施加任何要求”。
当您调用未定义的行为时,任何事情都会发生。该程序可能会崩溃,可能会输出奇怪的结果,或者它似乎可以正常运行。另外,如果您在同一编译器上使用不同的编译器或不同的优化设置,则未定义行为的表现方式可能会改变。
C标准在6.5.7p3节中针对按位移位运算符规定以下内容:
对每个操作数执行整数提升。的 结果的类型是提升后的左操作数的类型。 如果 右操作数的值是负数或大于 或等于提升的左操作数的宽度,其行为是 未定义。
在这种情况下,编译器可能会像您建议的那样减少以位宽度为模的移位量,或者可以将其视为以数学方式移位该量,导致所有位均为0。这是有效的结果,因为该标准未指定行为。
答案 1 :(得分:1)
不确定性的一个原因是8086,原始的x86,没有不掩盖移位计数中的任何位。相反,它实际上是通过每个位置使用一个时钟滴答来执行移位的。
然后,英特尔意识到允许255个以上的时钟节拍用于移位指令可能不是一个好主意。例如,他们可能考虑了最长的中断响应时间。
摘自我以前的80286手册:
为减少最大执行时间,iAPX 286不允许移位计数大于31。如果尝试的移位计数大于31,则仅使用移位计数的后五位。 iAPX 86使用移位计数的所有8位。
对于在PC / XT和PC / AT上完全相同的程序,您得到了不同的结果。
那么语言标准应该怎么说?
Java通过不使用底层硬件解决了这一问题。 C而是选择说效果不清楚。