为什么一个变量和一个数字的移位没有相同的结果?

Why doesn't the bit-shift of a variable and a number have the same result?

我正在移动一些位,然后才意识到使用变量进行运算的结果与使用数字的结果不同。请参阅下面的示例。

int a = 97;
int b = 0;

b = 1 << a;
printf("%d\n", b); 
// 2

b = 1 << 97;
printf("%d\n", b); 
// 0 - warning: shift count >= width of type [-Wshift-count-overflow]

您看到的警告是编译时警告。现在,你可以清楚地看到你的 int b 是一个 32 位变量,如果 left-shifted 97 次就会溢出。所以,这是一个合理的担忧。但是编译器只能检测到这种溢出,因为它是在编译期间评估的,并且编译器立即知道它会溢出。

在移位次数可变的情况下,编译器不够智能,无法知道 int a 归结为移位时将拥有什么值。因此,编译器将其留给您。

由于 left shift 的右操作数大于左操作数的位长度的结果未定义,因此表达式的任何结果都是可能的。

在可变情况下(1 << a),由于a是97(大于int中的位数),最可能的结果是1 << (97 % 32) == 1 << 1 == 20,通常取决于硬件 (CPU) 处理这些转变的方式。

使用常量 (1 << 97),编译器知道你移动得太远,发出警告(这不是必需的),并将结果定义为 0(也不是必需的) ).

这里的 C++ 标准中概述了未定义的行为。

http://eel.is/c++draft/expr.shift

The behavior is undefined if the right operand is negative, or greater than or equal to the width of the promoted left operand.

根据编译器和优化级别,您会得到不同的结果。如果打开优化,编译器会很容易地优化掉第一个移位操作,然后将其也设为 0。

为什么它会那样做呢?用于按变量移位的 x86 指令是 SAL (shift-arithmetic-left)。您可以在此处查看移位操作的指令列表:

https://c9x.me/x86/html/file_module_x86_id_285.html

将在未优化的构建中使用的是 SAL r/m32, CLCL 寄存器是 8 位,但处理器在内部将其屏蔽为 5 位:

The destination operand can be a register or a memory location. The count operand can be an immediate value or register CL. The count is masked to 5 bits, which limits the count range to 0 to 31. A special opcode encoding is provided for a count of 1.