C++ 为什么在使用超过 32 次时,32 位整数的左移位“<<”不能按预期工作?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7401888/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 16:52:50  来源:igfitidea点击:

Why doesn't left bit-shift, "<<", for 32-bit integers work as expected when used more than 32 times?

c++bit-shift

提问by AnkitSablok

When I write the following program and use the GNU C++ compiler, the output is 1which I think is due to the rotation operation performed by the compiler.

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

#include <iostream>

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

    return 0;
}

But logically, as it's said that the bits are lost if they overflow the bit width, the output should be 0. What is happening?

但从逻辑上说,如果位宽溢出,位就会丢失,输出应该是 0。这是怎么回事?

The code is on ideone, http://ideone.com/VPTwj.

代码位于 ideone http://ideone.com/VPTwj 上

采纳答案by Ashwin Nanjappa

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-31only. 1

这是由于 C 中未定义的行为以及为 IA-32 处理器生成的代码在移位计数上应用了 5 位掩码这一事实造成的。这意味着在 IA-32 处理器上,移位计数的范围仅为0-311

From The C programming language2

来自C 编程语言2

The result is undefined if the right operand is negative, or greater than or equal to the number of bits in the left expression's type.

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

From IA-32 Intel Architecture Software Developer's Manual3

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

The 8086 does not mask the shift count. However, all other IA-32 processors (starting with the Intel 286 processor) do mask the shift count to 5 bits, resulting in a maximum count of 31. This masking is done in all operating modes (including the virtual-8086 mode) to reduce the maximum execution time of the instructions.

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





1http://codeyarns.com/2004/12/20/c-shift-operator-mayhem/

1 http://codeyarns.com/2004/12/20/c-shift-operator-mayhem/

2A7.8 Shift Operators, Appendix A. Reference Manual, The C Programming Language

2A7.8 移位运算符,附录 A. 参考手册,C 编程语言

3SAL/SAR/SHL/SHR – Shift, Chapter 4. Instruction Set Reference, IA-32 Intel Architecture Software Developer's Manual

3SAL/SAR/SHL/SHR – Shift,第 4 章指令集参考,IA-32 英特尔架构软件开发人员手册

回答by Lindydancer

In C++, shift is only well-defined if you shift a value less steps than the size of the type. If intis 32 bits, then only 0 to, and including, 31 steps is well-defined.

在 C++ 中,如果您将值移动的步数少于类型的大小,则移动是明确定义的。如果int是 32 位,则只有 0 到 31 步(包括 31 步)是明确定义的。

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.

如果您查看执行移位的底层硬件,如果它只需要查看值的低五位(在 32 位情况下),则可以使用比必须检查更少的逻辑门来实现价值的每一点。

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 和 C++ 旨在在任何可用硬件上尽可能快地运行。今天,生成的代码只是一个“移位”指令,不管底层硬件如何处理指定范围之外的值。如果语言已经指定了 shift 应该如何表现,则生成的可能必须在执行 shift 之前检查 shift 计数是否在范围内。通常,这将产生三个指令(比较、分支、移位)。(诚​​然,在这种情况下,由于班次计数已知,因此没有必要。)

回答by David Heffernan

It's undefined behaviour according to the C++ standard:

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

The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1 × 2^E2, reduced modulo one more than the maximum value representable in the result type. Otherwise, if E1 has a signed type and non-negative value, and E1×2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

E1 << E2 的值是 E1 左移的 E2 位位置;空出的位用零填充。如果 E1 具有无符号类型,则结果的值为 E1 × 2^E2,比结果类型中可表示的最大值减少模 1。否则,如果 E1 具有有符号类型和非负值,并且 E1×2^E2 在结果类型中是可表示的,那么就是结果值;否则,行为是 undefined

回答by antonakos

The answers of Lindydancer and 6502 explain why (on some machines) it happens to be a 1that is being printed (although the behavior of the operation is undefined). I am adding the details in case they aren't obvious.

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

I am assuming that (like me) you are running the program on an Intel processor. GCC generates these assembly instructions for the shift operation:

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

movl , %ecx
sall %cl, %eax

On the topic of salland other shift operations, page 624 in the Instruction Set Reference Manualsays:

关于sall和其他移位操作的主题,指令集参考手册中的第 624 页说:

The 8086 does not mask the shift count. However, all other Intel Architecture processors (starting with the Intel 286 processor) do mask the shift count to five bits, resulting in a maximum count of 31. This masking is done in all operating modes (including the virtual-8086 mode) to reduce the maximum execution time of the instructions.

8086 不会屏蔽移位计数。但是,所有其他英特尔架构处理器(从英特尔 286 处理器开始)确实将移位计数屏蔽为 5 位,从而导致最大计数为 31。这种屏蔽在所有操作模式(包括虚拟 8086 模式)中进行,以减少指令的最大执行时间。

Since the lower 5 bits of 32 are zero, then 1 << 32is equivalent to 1 << 0, which is 1.

由于 32 的低 5 位为零,则1 << 32等价于1 << 0,即1

Experimenting with larger numbers, we would predict that

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

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

would print 1 2 4, and indeed that is what is happening on my machine.

会打印1 2 4,确实这就是我机器上发生的事情。

回答by 6502

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 documentationfor an explanation).

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

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.

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

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.

一种更“有用”和“合乎逻辑”的方法将是例如具有(x << y)等价(x >> -y)和处理具有逻辑和一致行为的高计数器。

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.

鉴于在这些情况下不同的硬件会做不同的事情,标准所说的基本上是“无论发生什么,当你做了奇怪的事情都不要责怪 C++,这是你的错”翻译成法律术语。

回答by DarkDust

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.

将 32 位变量移动 32 位或更多位是未定义的行为,可能会导致编译器使守护程序飞出您的鼻子。

Seriously, most of the time the output will be 0 (if intis 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.

说真的,大部分时间输出将为 0(如果int是 32 位或更少),因为您正在移动 1 直到它再次下降并且只剩下 0 为止。但是编译器可能会优化它来做任何它喜欢的事情。

See the excellent LLVM blog entry What Every C Programmer Should Know About Undefined Behavior, a must-read for every C developer.

请参阅优秀的 LLVM 博客条目每个 C 程序员应该知道的关于未定义行为的内容,这是每个 C 开发人员的必读之物。

回答by Bob2Chiv

Since you are bit shifting an int by 32 bits; you'll get: warning C4293: '<<' : shift count negative or too big, undefined behaviorin VS. This means that you're shifting beyond the integer and the answer could be ANYTHING, because it is undefined behavior.

由于您将 int 位移了 32 位;你会得到:warning C4293: '<<' : shift count negative or too big, undefined behavior在 VS 中。这意味着您正在超越整数,答案可能是任何东西,因为它是未定义的行为。

回答by Sandip Agarwal

You could try the following. This actually gives the output as 0after 32left shifts.

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

#include<iostream>
#include<cstdio>

using namespace std;

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

回答by Pieter888

I had the same problem and this worked for me:

我遇到了同样的问题,这对我有用:

f = ((long long)1 << (i-1));

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.

其中 i 可以是大于 32 位的任何整数。1 必须是 64 位整数才能进行移位。