当使用超过32次时,32位整数为什么不按预期工作,为什么不移位"<<"?

Ank*_*lok 59 c++ bit-shift

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

#include <iostream>

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

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

但从逻辑上讲,正如所说的那样,如果位溢出位宽就会丢失,输出应为0.发生了什么?

代码在ideone上,http: //ideone.com/VPTwj .

Lin*_*cer 44

在C++中,如果将值移动的步长少于类型的大小,则只能很好地定义shift.如果int是32位,那么只有0到31,并且包括31步是明确定义的.

那么,这是为什么呢?

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

在评论中回答问题

C和C++旨在在任何可用的硬件上尽可能快地运行.今天,生成的代码只是一个"移位"指令,无论底层硬件如何处理指定范围之外的值.如果语言已经指定了移位应该如何表现,则生成的可能必须在执行移位之前检查移位计数是否在范围内.通常,这将产生三个指令(比较,分支,移位).(不可否认,在这种情况下,由于移位计数已知,因此没有必要.)

  • @Beta:"不要为你不需要的东西买单"原则.**如果您的代码需要,您**可以进行检查.但如果检查是自动的,你就不能这样做. (10认同)
  • @Beta - 它是未定义的,以免妨碍实现,在软件中模拟标准构思时硬件中的"标准". (3认同)
  • @Beta:有趣的问题!大多数未定义的操作直接映射到处理器指令.行为通常是*undefined*,因为否则需要在操作之前进行检查,而这些检查在大多数体系结构上都无法有效实现.典型的安全/速度贸易. (2认同)

Ash*_*ppa 42

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

来自C编程语言 2

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

来自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 - 移位,第4章指令集参考,IA-32"英特尔架构软件开发人员手册"

  • 您可以在答案中加入该博客文章的精髓(参见http://meta.stackexchange.com/a/8259/244745)吗? (4认同)

Dav*_*nan 21

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

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

  • 这是一个强大的快速标准搜索! (5认同)
  • @awoodland是的,Google令人印象深刻! (3认同)
  • @Ankit答案不应该是0.答案是不确定的.答案可能是任何事情.这是未定义行为的本质. (3认同)
  • @AnkitSablok未定义的行为意味着可能发生任何行为;)检查:http://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior (2认同)
  • @AnkitSablok - 它证明了这种行为的合理性.关键是当行为未定义时,允许编译器做任何事情.最好的猜测,编译器只是决定什么也不做(根据标准完全有效) (2认同)
  • 这不是最好引用的部分。上一段指出“如果右操作数为负数,或者大于或等于提升的左操作数的位长度,则行为未定义”,因此无论左操作数的符号如何,`n &lt;&lt; 32` 都是 UB (2认同)

ant*_*kos 13

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

我假设(像我一样)你在英特尔处理器上运行程序.GCC为换档操作生成以下装配说明:

movl $32, %ecx
sall %cl, %eax
Run Code Online (Sandbox Code Playgroud)

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

8086不掩盖班次计数.但是,所有其他英特尔架构处理器(从英特尔286处理器开始)确实将移位计数屏蔽为5位,最大计数为31.此屏蔽在所有操作模式(包括虚拟8086模式)下完成,以减少指令的最长执行时间.

由于32的低5位是零,1 << 32所以相当于1 << 0,即1.

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

cout << (a << 32) << " " << (a << 33) << " " << (a << 34) << "\n";
Run Code Online (Sandbox Code Playgroud)

会印刷1 2 4,事实上这就是我机器上发生的事情.


650*_*502 9

它没有按预期工作,因为你期望太多.

在x86的情况下,硬件不关心计数器大于寄存器大小的移位操作(例如,参见x86参考文档中的SHL指令描述).

C++标准不希望通过告诉在这些情况下要做什么来增加额外成本,因为生成的代码将被迫为每个参数移位添加额外的检查和逻辑.

有了这种自由,编译器的实现者只需生成一个汇编指令而无需任何测试或分支.

更"有用"和"逻辑"的做法本来例如有(x << y)相当于(x >> -y)也高柜与逻辑和一致的行为的处理.

然而,这需要更慢的位移处理,因此选择是做硬件所做的事情,让程序员需要为侧面情况编写自己的函数.

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


Dar*_*ust 8

将32位变量移动32位或更多位是未定义的行为,并且可能导致编译器使守护进程从您的鼻子中飞出.

严重的是,大多数情况下输出将为0(如果int是32位或更少),因为你正在移动1直到它再次下降并且只剩下0.但是编译器可以优化它以做任何它喜欢的事情.

查看优秀的LLVM博客文章每个C程序员应该知道的关于未定义行为的内容,这是每个C开发人员必读的内容.


Bob*_*hiv 5

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