当我编写以下程序并使用GNU C ++编译器时,输出为1
,我认为这是由编译器执行的旋转操作引起的。
#include <iostream>
int main()
{
int a = 1;
std::cout << (a << 32) << std::endl;
return 0;
}
但从逻辑上讲,正如所说的那样,如果位溢出位宽就会丢失,输出应为0.发生了什么事?
代码位于ideone http://ideone.com/VPTwj。
答案 0 :(得分:43)
在C ++中,如果将值移动的步长小于类型的大小,则只能很好地定义shift。如果int
是32位,那么只有0到(包括)31步是明确定义的。
那么,为什么会这样?
如果你看看执行移位的底层硬件,如果它只需要查看一个值的低五位(在32位的情况下),它可以使用比它更少的逻辑门实现必须检查价值的每一点。
在评论中回答问题
C和C ++旨在在任何可用硬件上尽可能快地运行。今天,生成的代码只是一个“移位”指令,无论底层硬件如何处理指定范围之外的值。如果语言已经指定了移位应该如何表现,则生成的可能必须在执行移位之前检查移位计数是否在范围内。通常,这将产生三个指令(比较,分支,移位)。 (无可否认,在这种情况下,由于移位计数已知,因此没有必要。)
答案 1 :(得分:40)
这是由于C中未定义的行为和为IA-32处理器生成的代码在移位计数上应用了5位掩码这一事实引起的。这意味着在IA-32处理器上,移位计数的范围仅为 0-31 。 1
来自 C编程语言 2
如果右操作数为负数,或者大于或等于左表达式类型中的位数,则结果未定义。
来自 IA-32英特尔架构软件开发人员手册 3
8086不会掩盖班次计数。但是,所有其他IA-32处理器(从Intel 286处理器开始)确实将移位计数屏蔽为5位,最大计数为31.此屏蔽在所有操作模式(包括虚拟8086模式)下完成减少指令的最长执行时间。
1 http://codeyarns.com/2004/12/20/c-shift-operator-mayhem/
2 A7.8移位运算符,附录A.参考手册,C语言程序设计
3 SAL / SAR / SHL / SHR - Shift,第4章指令集参考,IA-32英特尔架构软件开发人员手册
答案 2 :(得分:21)
根据C ++标准,它是未定义的行为:
E1的值<&lt; E2是E1 左移E2位位置;腾空 位是零填充的。如果E1有 unsigned type,结果的值 是E1×2 ^ E2,减少模数 比最大值可表示 在结果类型中。否则,如果是E1 有一个签名类型和非负面 值,E1×2 ^ E2可表示 结果类型,那就是 结果价值; 否则, 行为未定义。
答案 3 :(得分:12)
Lindydancer和6502的答案解释了为什么(在某些机器上)恰好是正在打印的1
(尽管操作的行为未定义)。我正在添加细节,以防它们不明显。
我假设(像我一样)你在英特尔处理器上运行程序。 GCC为换档操作生成这些装配说明:
movl $32, %ecx
sall %cl, %eax
关于sall
和其他班次操作的主题,Instruction Set Reference Manual中的第624页说:
8086不会掩盖班次计数。但是,所有其他英特尔架构处理器 (从Intel 286处理器开始)将移位计数屏蔽为5位,从而产生a 最大计数为31.此屏蔽在所有操作模式下完成(包括virtual-8086 mode)减少指令的最大执行时间。
由于32的低5位为零,因此1 << 32
相当于1 << 0
,即1
。
尝试使用更大的数字,我们会预测
cout << (a << 32) << " " << (a << 33) << " " << (a << 34) << "\n";
会打印1 2 4
,这确实就是我机器上发生的事情。
答案 4 :(得分:9)
它没有按预期工作,因为你期望太多。
对于x86,硬件不关心计数器大于寄存器大小的移位操作(例如,参见x86 reference documentation上的SHL指令说明)。
C ++标准不希望通过告诉在这些情况下要做什么来增加额外成本,因为生成的代码将被迫为每个参数转换添加额外的检查和逻辑。
通过这种自由,编译器的实现者只需生成一个汇编指令而无需任何测试或分支。
例如,更“有用”和“逻辑”的方法是(x << y)
等效于(x >> -y)
,并且处理具有逻辑和一致行为的高计数器。
然而,这需要更慢的位移处理,因此选择是做硬件所做的事情,让程序员需要为侧面情况编写自己的函数。
鉴于不同的硬件在这些情况下做了不同的事情,标准所说的基本上是“当你做奇怪的事情时发生什么,不要责怪C ++,这是你的错”,翻译成法律术语。
答案 5 :(得分:8)
将32位变量移动32位或更多位是未定义的行为,可能会导致编译器使守护进程从你的鼻子中飞出。
严重的是,大多数情况下输出为0(如果int
为32位或更少),因为你正在移动1直到它再次下降并且只剩下0。但编译器可能会对其进行优化以做任何他们喜欢的事情。
查看优秀的LLVM博客文章What Every C Programmer Should Know About Undefined Behavior,这是每个C开发人员必读的内容。
答案 6 :(得分:5)
因为你将int移位32位;你会得到:warning C4293: '<<' : shift count negative or too big, undefined behavior
在VS.这意味着你正在超越整数,答案可能是任何一个,因为它是未定义的行为。
答案 7 :(得分:0)
您可以尝试以下方法。实际上,在0
左移后,输出为32
。
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int a = 1;
a <<= 31;
cout << (a <<= 1);
return 0;
}
答案 8 :(得分:0)
我遇到了同样的问题,这对我有用:
f =((长)1 <&lt;(i-1));
其中我可以是任何大于32位的整数。 1必须是64位整数才能使转换工作。