C 为什么将一个变量移位超过它的宽度(以位为单位)就等于零?

C 为什么将一个变量移位超过它的宽度(以位为单位)就等于零?,c,undefined-behavior,bit-shift,C,Undefined Behavior,Bit Shift,这个问题受到StackOverflow其他问题的启发。今天,在浏览StackOverflow时,我遇到了一个问题,即用大于等于该变量的宽度(以位为单位)的值k对变量进行位移位。这意味着将32位整数移位32位或更多位 从这些问题中可以明显看出,如果我们试图将一个数移位k位,即>=变量的位宽度,则只取最低有效的log2k位。对于32位整数,最低有效5位被屏蔽,并作为移位量 所以一般来说,如果w=变量的宽度(以位为单位), x>>k变成x>>k%w 对于int,这是x>>k%32 计数被屏蔽为5位,这

这个问题受到StackOverflow其他问题的启发。今天,在浏览StackOverflow时,我遇到了一个问题,即用大于等于该变量的宽度(以位为单位)的值k对变量进行位移位。这意味着将32位整数移位32位或更多位

从这些问题中可以明显看出,如果我们试图将一个数移位k位,即>=变量的位宽度,则只取最低有效的log2k位。对于32位整数,最低有效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编程语言的一条规则


链接问题明确指出,如果移位量大于被移位类型的位宽度,则在使用不可移植或错误的程序结构或错误数据时,本标准将调用该类型,该行为定义为行为,本国际标准对此不作任何要求

当您调用未定义的行为时,任何事情都可能发生。程序可能会崩溃,可能会输出奇怪的结果,或者看起来工作正常。此外,如果在同一编译器上使用不同的编译器或不同的优化设置,则未定义行为的显示方式可能会发生变化

C标准规定了第6.5.7p3节中关于位移位运算符的以下内容:

对每个操作数执行整数提升。这个 结果的类型是提升的左操作数的类型。如果 右操作数的值为负或大于 或等于提升后的左操作数的宽度,则行为为 未定义


在这种情况下,编译器可能会如您所建议的那样,将移位量减少到位宽度的模,或者将其视为在数学上移位该量,从而导致所有位都为0。因为标准没有指定行为,所以两者都是有效的结果。

未定义的一个原因是8086(原始x86)没有屏蔽移位计数中的任何位。相反,它实际上执行了移位,每个位置使用一个时钟滴答声

英特尔随后意识到,允许移位指令使用255+时钟滴答声可能不是一个好主意。例如,他们可能考虑了最大中断响应时间

从我的旧80286手册:

为了减少最大执行时间,iAPX 286不允许移位计数大于31。如果尝试移位计数大于31,则仅使用移位计数的底部五位。iAPX 86使用移位计数的所有8位

对于PC/XT和PC/AT上的完全相同的程序,这会给出不同的结果

那么语言标准应该说什么呢


Java通过不使用底层硬件解决了这个问题。C反而选择说效果不清楚。

好吧,我不知道语言规范会说什么,但我总是认为移位是从一边去掉一点,在另一边插入一个0。因此,使用这种逻辑,移位的位数大于或等于变量的宽度,最终肯定会产生全零。您链接的问题表明该行为未定义,因此所有赌注均已取消。您链接的问题引用了标准:如果右操作数为负,或大于或等于提升后的左操作数的位长度,则该行为未定义。如果36%32==4,则应将变量移位4字节,你从哪里得到减少通过模移位的位置数的想法?在你提到的两个公认的答案中,我都看不到这一点。Java与任何东西有什么关系?问题的最后一部分似乎偏离了主题。要使80386与80286类似,它应该在移位32位值时查看底部的六位,因为精确地按字大小移位有时是一种有用的角情况。IIRC,x186确实屏蔽了移位计数 而x86没有。一种在运行时检测差异的方法。