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

发布于 2024-12-04 08:27:15 字数 372 浏览 2 评论 0 原文

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

#include <iostream>

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

    return 0;
}

但从逻辑上讲,正如所说的那样,如果溢出位宽,这些位就会丢失,因此输出应该为 0。这是怎么回事?

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

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

#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?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

↘人皮目录ツ 2024-12-11 08:27:15

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

来自 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 英特尔架构软件开发人员手册

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

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 Manual 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.


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

一身骄傲 2024-12-11 08:27:15

在 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.)

输什么也不输骨气 2024-12-11 08:27:15

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

E1的值<< E2是E1
左移 E2 位位置;腾出
位用零填充。如果E1有一个
无符号类型,结果的值
是 E1 × 2^E2,再模一减少
大于可表示的最大值
在结果类型中。否则,如果E1
具有有符号类型和非负数
值,E1×2^E2 可以表示为
结果类型,那就是
结果值; 否则,
行为未定义

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

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
.

拥抱影子 2024-12-11 08:27:15

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

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

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

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

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

由于 32 的低 5 位为零,因此 1 << 32 相当于 1 << 0,即1

通过尝试更大的数字,我们预测会

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

打印 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:

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

On the topic of sall and other shift operations, page 624 in the Instruction Set Reference Manual says:

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.

Since the lower 5 bits of 32 are zero, then 1 << 32 is equivalent to 1 << 0, which is 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.

权谋诡计 2024-12-11 08:27:15

它没有按预期工作,因为您期望太多。

在 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.

歌枕肩 2024-12-11 08:27:15

将 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.

傻比既视感 2024-12-11 08:27:15

由于您将 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.

死开点丶别碍眼 2024-12-11 08:27:15

你可以尝试以下方法。这实际上在 32 左移后输出为 0

#include<iostream>
#include<cstdio>

using namespace std;

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

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

#include<iostream>
#include<cstdio>

using namespace std;

int main()
{
  int a = 1;
  a <<= 31;
  cout << (a <<= 1);
  return 0;
}
霊感 2024-12-11 08:27:15

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

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.

若有似无的小暗淡 2024-12-11 08:27:15

尝试使用 1LL << 60 。这里的LL代表long long。您现在可以移位到最多 61 位。

Try using 1LL << 60. Here LL is for long long. You can shift now to max of 61 bits.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文