对于32位整数,如果使用次数超过32次,为什么不使用左位移(“ < <”)呢?

当我编写以下程序并使用 GNU C + + 编译器时,输出是 1,我认为这是由于编译器执行了旋转操作。

#include <iostream>


int main()
{
int a = 1;
std::cout << (a << 32) << std::endl;


return 0;
}

但是从逻辑上讲,正如我们所说的,如果它们溢出位宽度,就会丢失位,那么输出应该是0。发生什么事了?

密码是一号 http://ideone.com/VPTwj

61616 次浏览

根据 C + + 标准,这是一种未定义的行为:

E1 < < E2为 E1 左移位 E2位位置; 空位 位是零填充的。如果 E1有一个 无符号类型,则结果的值为 是 E1 × 2 ^ E2,再减一个模 超过可表示的最大值 在结果类型中。否则,如果 E1 有符号类型和非否定类型 值,E1 × 2 ^ E2在 结果类型,那么它就是 结果值; < strong > 否则,将引发 行为是未定义的

将一个32位变量移动32位或更多位是未定义行为的,可能会导致编译器让守护进程从你的鼻子里飞出来。

严重的是,大多数时候输出将是0(如果 int是32位或更少) ,因为您正在移动1,直到它再次下降,除了0什么也没有留下。但是编译器可以对它进行优化,使它可以做任何它喜欢的事情。

查看优秀的 LLVM 博客条目 每个 c 程序员都应该知道的未定义行为,这是每个 C 开发人员必读的。

在 C + + 中,移位只有在移动值的步长小于类型的大小时才能得到很好的定义。如果 int是32位,那么只有0到(包括)31步是定义良好的。

为什么会这样?

如果你看一下执行移位的底层硬件,如果它只需要看一个值的下5位(在32位的情况下) ,它可以使用更少的逻辑门实现,而不是如果它必须检查每一位的值。

在评论中回答问题

C 和 C + + 被设计为在任何可用的硬件上尽可能快地运行。今天,生成的代码只是一个“ shift”指令,不管底层硬件如何处理指定范围之外的值。如果语言已经指定了 shift 的行为,那么生成的代码在执行 shift 之前必须检查 shift 计数是否在范围内。通常,这将产生三个指令(比较、分支、移位)。(不可否认,在这种情况下,没有必要这样做,因为我们知道换班次数。)

因为你把一个 int 移动了32位,你会得到: warning C4293: '<<' : shift count negative or too big, undefined behavior in vs 这意味着你移动了整数以外的东西,答案可能是任何东西,因为它是未定义行为的。

它不像预期的那样工作,因为你期望太多了。

在 x86的情况下,硬件不关心计数器大于寄存器大小的移位操作(参见 X86参考文档上的 SHL 指令描述以获得解释)。

C + + 标准不想通过告诉在这些情况下要做什么来增加额外的成本,因为生成的代码将被迫为每个参数转移添加额外的检查和逻辑。

有了这种自由度,编译器的实现者可以只生成一条汇编指令,而不需要任何测试或分支。

一个更“有用”和“合乎逻辑”的方法是,例如使 (x << y)等效于 (x >> -y),并且使用逻辑和一致的行为处理高计数器。

然而,这将需要一个更慢的处理位移,所以选择是做硬件做什么,留给程序员需要编写自己的副案的函数。

考虑到不同的硬件在这些情况下做不同的事情,标准基本上是这样说的: “无论发生什么,当你做奇怪的事情只是不要责怪 C + + ,这是你的错误”翻译成法律术语。

Lindydance 和6502的答案解释了为什么(在一些机器上)它碰巧是一个正在打印的 1(尽管操作的行为是未定义的)。我在添加细节,以防它们不明显。

我假设(像我一样)你正在英特尔处理器上运行程序。GCC 为移位操作生成这些汇编指令:

movl $32, %ecx
sall %cl, %eax

关于 sall和其他班次操作的话题,指令集参考手册第624页写道:

8086不掩盖移位计数。然而,所有其他英特尔架构处理器 (从英特尔286处理器开始)将移位计数屏蔽到5位,从而导致 最大计数为31。此屏蔽在所有操作模式下完成(包括虚拟 -8086 模式) ,以减少指令的最大执行时间。

因为32的下5位是零,那么 1 << 32等价于 1 << 0,也就是 1

用更大的数字进行实验,我们可以预测

cout << (a << 32) << " " << (a << 33) << " " << (a << 34) << "\n";

将打印 1 2 4,实际上这就是我的机器上正在发生的事情。

您可以尝试以下操作。这实际上给出了 32左移之后的 0输出。

#include<iostream>
#include<cstdio>


using namespace std;


int main()
{
int a = 1;
a <<= 31;
cout << (a <<= 1);
return 0;
}

这是由于 C 语言中未定义的行为和为 IA-32处理器生成的代码在移位计数上应用了一个5位掩码的结合造成的。这意味着在 IA-32处理器上,移位计数的范围仅为 0-31翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳

来自 C 编程语言 2

如果右操作数为负,或者大于或等于左表达式类型中的位数,则结果是未定义的。

来自 IA-32英特尔架构软件开发者手册 3

8086不会掩盖换班次数。然而,所有其他的 IA-32处理器(从 Intel 286处理器开始)确实将移位计数掩盖到5位,从而导致最大移位计数为31。这种屏蔽在所有操作模式(包括虚拟 -8086模式)中进行,以减少指令的最大执行时间。



翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳 http://codeyarns.com/2004/12/20/c-shift-operator-mayhem/

2 A7.8 Shift 操作符,附录 A 参考手册,C 编程语言

3 SAL/SAR/SHL/SHR-Shift,第4章,指令集参考,IA-32 Intel 架构软件开发者手册

我也有同样的问题,这对我很有效:

F = ((长长)1 < (i-1)) ;

I 可以是任意大于32位的整数。1必须是一个64位整数,才能进行移位操作。

尝试使用 1LL << 60。这里的 LLlong long。你现在可以转移到最大61位。