为什么将变量移位超过其宽度(以位为单位)会清零?

时间:2018-07-03 01:51:29

标签: c undefined-behavior bit-shift

此问题的灵感来自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

Weird result of Java Integer left shift

java : shift distance for int restricted to 31 bits

2 个答案:

答案 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而是选择说效果不清楚。