为什么32位整数不会像以前那样使用32位以上的“?”?
当我编写下面的程序并使用GNU C ++编译器时,输出是1
,我认为这是由于编译器执行的旋转操作。
#include <iostream> int main() { int a = 1; std::cout << (a << 32) << std::endl; return 0; }
但是从逻辑上讲,如果它们溢出了位宽,那么位会丢失,那么输出应该是0.发生了什么?
代码位于ideone, http: //ideone.com/VPTwj。
这是由于C中未定义行为的组合以及为IA-32处理器生成的代码在移位计数上应用了5位掩码的事实造成的。 这意味着在IA-32处理器上,移位计数的范围仅为0-31 。 1
从C编程语言 2
如果右操作数是负数,或者大于或等于左expression式types的位数,结果是不确定的。
从IA-32英特尔架构软件开发人员手册 3
8086不掩盖移位计数。 但是,所有其他IA-32处理器(从Intel 286处理器开始)都会将移位计数屏蔽到5位,导致最大计数为31.此屏蔽在所有操作模式(包括虚拟8086模式)减less指令的最大执行时间。
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英特尔架构软件开发人员手册
在C ++中,如果您将某个值的移动次数less于该types的大小,则只会定义移位。 如果int
是32位,那么只有0到31个步骤是明确的。
那么,为什么呢?
如果你看一下执行转换的底层硬件,如果只需要查看一个值的低5位(在32位的情况下),那么可以使用较less的逻辑门来实现,而不是如果它必须检查每一点的价值。
在评论中回答问题
C和C ++被devise为在任何可用的硬件上尽可能快地运行。 如今,生成的代码只是一个“shift”指令,无论底层硬件如何处理指定范围以外的值。 如果语言指定了换档应该如何进行,那么在执行换档之前,生成的可能必须检查换档计数是否在范围内。 通常,这将产生三个指令(比较,分支,移位)。 (不可否认,在这种情况下,这是不必要的,因为移位数是已知的。)
根据C ++标准,这是未定义的行为:
E1 << E2的值是E1左移E2位的位置; 空位被零填充。 如果E1为无符号types,则结果的值为E1×2E ^ 2,比结果types中可表示的最大值减1。 否则,如果E1有符号types和非负值,E1×2E ^ 2在结果types中可以表示,那么结果是值; 否则,行为是不确定的 。
Lindydancer和6502的答案解释了为什么(在某些机器上)恰好是正在打印的1
(虽然操作的行为是未定义的)。 如果他们不明显,我将增加细节。
我假设(像我一样)在Intel处理器上运行程序。 GCC为换class操作生成这些汇编指令:
movl $32, %ecx sall %cl, %eax
在sall
和其他移位操作的主题上, 指令集参考手册的第624页说:
8086不掩盖移位计数。 但是,所有其他英特尔架构处理器(从英特尔286处理器开始)都会将移位计数屏蔽为5位,导致最大计数为31.此屏蔽在所有操作模式(包括虚拟8086模式)下都可以减less指令的最大执行时间。
由于32的低5位是零,所以1 << 32
相当于1 << 0
,即1
。
试用更大的数字,我们可以预测
cout << (a << 32) << " " << (a << 33) << " " << (a << 34) << "\n";
会打印1 2 4
,事实上,这是我的机器上正在发生的事情。
它没有按预期工作,因为你期望太多。
在x86的情况下,硬件不关心计数器大于寄存器大小的移位操作(例如,请参阅x86参考文档中的SHL指令说明以获得解释)。
C ++标准不希望通过告知在这些情况下做什么来增加额外的成本,因为生成的代码将被迫为每个参数化转变添加额外的检查和逻辑。
有了这个自由,编译器的实现者只需要生成一个汇编指令,而无需任何testing或分支。
比较“有用”和“合乎逻辑”的方法是例如使(x << y)
等价于(x >> -y)
,并且处理具有逻辑和一致行为的高计数器。
然而,这将需要更慢的位移处理,所以select做什么硬件,留给程序员需要编写自己的函数的副作用。
鉴于不同的硬件在这些情况下做了不同的事情,标准所说的基本上是“无论发生什么事情,当你做了奇怪的事情,只是不要责怪C ++,这是你的错”,这是翻译成法律的。
将32位variables移位32位或更多位是未定义的行为,并可能导致编译器使守护进程飞出你的鼻子。
严重的是,大部分时间的输出将是0(如果int
是32位或更less),因为你正在移动1,直到它再次下降,只剩下0。 但是编译器可能会优化它来做任何喜欢的事情。
查看优秀的LLVM博客条目每个C程序员应该了解的关于未定义行为 ,每个C开发人员都必须阅读。
由于你将位移位了32位, 你会得到: warning C4293: '<<' : shift count negative or too big, undefined behavior
VS中的warning C4293: '<<' : shift count negative or too big, undefined behavior
。 这意味着你正在超越整数,答案可能是任何的,因为它是未定义的行为。
你可以尝试以下。 这实际上在32
左移之后给出输出为0
。
#include<iostream> #include<cstdio> using namespace std; int main() { int a = 1; a <<= 31; cout << (a <<= 1); return 0; }
我有同样的问题,这对我工作:
f =((long long)1 <<(i-1));
我可以是任何大于32位的整数。 1必须是一个64位整数转换工作。