为什么 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。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
这是由于 C 中未定义的行为以及为 IA-32 处理器生成的代码在移位计数上应用了 5 位掩码这一事实共同造成的。这意味着在 IA-32 处理器上,移位计数的范围仅为0-31。 1
来自 C 编程语言 2
摘自IA-32英特尔架构软件开发人员手册 3
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 英特尔架构软件开发人员手册
This is caused due to a combination of an undefined behaviour in C and the fact that code generated for IA-32 processors has a 5 bit mask applied on the shift count. This means that on IA-32 processors, the range of a shift count is 0-31 only. 1
From The C programming language 2
From IA-32 Intel Architecture Software Developer’s Manual 3
1 http://codeyarns.com/2004/12/20/c-shift-operator-mayhem/
2 A7.8 Shift Operators, Appendix A. Reference Manual, The C Programming Language
3 SAL/SAR/SHL/SHR – Shift, Chapter 4. Instruction Set Reference, IA-32 Intel Architecture Software Developer’s Manual
在 C++ 中,仅当移动值的步数小于类型大小时,移位才是明确定义的。如果 int 是 32 位,则只有 0 到(包括 31)步是明确定义的。
那么,这是为什么呢?
如果您查看执行移位的底层硬件,如果它只需查看值的低五位(在 32 位情况下),则可以使用比必须检查更少的逻辑门来实现每一点的价值。
回答评论中的问题
C 和 C++ 旨在在任何可用的硬件上尽可能快地运行。如今,生成的代码只是一条“移位”指令,无论底层硬件如何处理指定范围之外的值。如果语言指定了移位的行为方式,则生成的语言可能必须在执行移位之前检查移位计数是否在范围内。通常,这会产生三个指令(比较、分支、移位)。 (诚然,在这种情况下,没有必要,因为班次计数是已知的。)
In C++, shift is only well-defined if you shift a value less steps than the size of the type. If
int
is 32 bits, then only 0 to, and including, 31 steps is well-defined.So, why is this?
If you take a look at the underlying hardware that performs the shift, if it only has to look at the lower five bits of a value (in the 32 bit case), it can be implemented using less logical gates than if it has to inspect every bit of the value.
Answer to question in comment
C and C++ are designed to run as fast as possible, on any available hardware. Today, the generated code is simply a ''shift'' instruction, regardless how the underlying hardware handles values outside the specified range. If the languages would have specified how shift should behave, the generated could would have to check that the shift count is in range before performing the shift. Typically, this would yield three instructions (compare, branch, shift). (Admittedly, in this case it would not be necessary as the shift count is known.)
根据 C++ 标准,这是未定义的行为:
It's undefined behaviour according to the C++ standard:
Lindydancer 和 6502 的答案解释了为什么(在某些机器上)它恰好是被打印的
1
(尽管操作的行为未定义)。我正在添加细节,以防它们不明显。我假设(像我一样)您正在英特尔处理器上运行该程序。 GCC 为移位操作生成这些汇编指令:
关于
sall
和其他移位操作的主题,指令集参考手册 说:由于 32 的低 5 位为零,因此
1 << 32
相当于1 << 0
,即1
。通过尝试更大的数字,我们预测会
打印
1 2 4
,事实上这就是我的机器上发生的情况。The answers of Lindydancer and 6502 explain why (on some machines) it happens to be a
1
that is being printed (although the behavior of the operation is undefined). I am adding the details in case they aren't obvious.I am assuming that (like me) you are running the program on an Intel processor. GCC generates these assembly instructions for the shift operation:
On the topic of
sall
and other shift operations, page 624 in the Instruction Set Reference Manual says:Since the lower 5 bits of 32 are zero, then
1 << 32
is equivalent to1 << 0
, which is1
.Experimenting with larger numbers, we would predict that
would print
1 2 4
, and indeed that is what is happening on my machine.它没有按预期工作,因为您期望太多。
在 x86 的情况下,硬件不关心计数器大于寄存器大小的移位操作(例如,请参阅 x86 参考文档以获取解释)。
C++ 标准不想通过告诉在这些情况下要做什么来施加额外的成本,因为生成的代码将被迫为每个参数转换添加额外的检查和逻辑。
有了这种自由,编译器的实现者可以只生成一条汇编指令,而无需任何测试或分支。
例如,更“有用”和“合乎逻辑”的方法是让
(x << y)
等价于(x >> -y)
以及以逻辑一致的行为处理高计数器。然而,这需要更慢地处理位移位,因此选择是做硬件所做的事情,而程序员则需要为边际情况编写自己的函数。
考虑到不同的硬件在这些情况下会做不同的事情,标准所说的基本上是“当你做奇怪的事情时发生什么,不要责怪 C++,这是你的错”翻译成法律术语。
It doesn't work as expected because you're expecting too much.
In the case of x86 the hardware doesn't care about shift operations where the counter is bigger than the size of the register (see for example SHL instruction description on x86 reference documentation for an explanation).
The C++ standard didn't want to impose an extra cost by telling what to do in these cases because generated code would have been forced to add extra checks and logic for every parametric shift.
With this freedom implementers of compilers can generate just one assembly instruction without any test or branch.
A more "useful" and "logical" approach would have been for example to have
(x << y)
equivalent to(x >> -y)
and also the handling of high counters with a logical and consistent behavior.However this would have required a much slower handling for bit shifting so the choice was to do what the hardware does, leaving to the programmers the need to write their own functions for side cases.
Given that different hardware does different things in these cases what the standard says is basically "Whatever happens when you do strange things just don't blame C++, it's your fault" translated in legalese.
将 32 位变量移位 32 位或更多位是未定义的行为,可能会导致编译器让守护进程从你的鼻子里飞出来。
说真的,大多数时候输出将为 0(如果
int
为 32 位或更少),因为您正在移动 1,直到它再次下降,只剩下 0。但编译器可能会对其进行优化以执行其喜欢的操作。请参阅优秀的 LLVM 博客文章 每个 C 程序员都知道什么《应该了解未定义行为》,每个 C 开发人员的必读之作。
Shifting a 32 bit variable by 32 or more bits is undefined behavior and may cause the compiler to make daemons fly out of your nose.
Seriously, most of the time the output will be 0 (if
int
is 32 bits or less) since you're shifting the 1 until it drops off again and nothing but 0 is left. But the compiler may optimize it to do whatever it likes.See the excellent LLVM blog entry What Every C Programmer Should Know About Undefined Behavior, a must-read for every C developer.
由于您将 int 移位了 32 位;你会得到:
警告 C4293: '<<' : VS 中的移位计数为负或太大,未定义行为
。这意味着您正在超出整数范围,答案可能是任何东西,因为它是未定义的行为。Since you are bit shifting an int by 32 bits; you'll get:
warning C4293: '<<' : shift count negative or too big, undefined behavior
in VS. This means that you're shifting beyond the integer and the answer could be ANYTHING, because it is undefined behavior.你可以尝试以下方法。这实际上在
32
左移后输出为0
。You could try the following. This actually gives the output as
0
after32
left shifts.我遇到了同样的问题,这对我有用:
f = ((long long)1 << (i-1));
其中 i 可以是任何大于 32 位的整数。 1 必须是 64 位整数才能进行移位。
I had the same problem and this worked for me:
f = ((long long)1 << (i-1));
Where i can be any integer bigger than 32 bits. The 1 has to be a 64 bit integer for the shifting to work.
尝试使用
1LL << 60
。这里的LL
代表long long
。您现在可以移位到最多 61 位。Try using
1LL << 60
. HereLL
is forlong long
. You can shift now to max of 61 bits.