【问题标题】:Shift Operators in C++C++ 中的移位运算符
【发布时间】:2011-02-02 14:48:52
【问题描述】:

如果移位运算符后面的值 大于中的位数 左边的操作数,结果是 不明确的。如果左边的操作数是 无符号,右移是一个逻辑 移位所以高位将被填充 用零。如果左边的操作数 已签名,右移可能或可能 不是一个逻辑转变(也就是说, 行为未定义)。

谁能解释一下上面几行是什么意思?

【问题讨论】:

  • 有道理,有没有你不熟悉的具体技术术语?换句话说,我们如何回答这个问题而不只是解释上面的引用,这可能对你有帮助,也可能没有帮助。
  • 另外,你从哪里得到的文本?这不是 ISO/IEC 14882:1998(E) 的 5.8,我可以告诉你这么多。
  • 投票结束。文本未出处,不适用于 C++。
  • @David:好像是来自Thinking in C++: linuxtopia.org/online_books/programming_books/thinking_in_c++/…
  • @David:Google 告诉我它来自 Bruce Eckel 的 Thinking in C++。如果你把你的书放到网上,而不用更正注释,你基本上就是鱼在桶里;-)

标签: c++ operators bit-shift


【解决方案1】:

这些行的意思并不重要,它们基本上是不正确的。

"如果移位运算符后面的值 大于中的位数 左边的操作数,结果是 未定义。”

是真的,但应该说“大于或等于”。 5.8/1:

... 如果 右手操作数为负数,或 大于或等于长度 提升的左操作数的位。

未定义的行为意味着“不要这样做”(见下文)。也就是说,如果int 在您的系统上是 32 位,那么您不能有效地执行以下任何操作:

int a = 0; // this is OK
a >> 32;   // undefined behavior
a >> -1;   // UB
a << 32;   // UB
a = (0 << 32); // Either UB, or possibly an ill-formed program. I'm not sure.

“如果左侧操作数是无符号的,则右移是逻辑移位,因此高位将用零填充。”

这是真的。 5.8/3 说:

如果 E1 具有无符号类型或 E1 具有 有符号类型和非负值, 结果是不可分割的一部分 E1 的商除以数量 2 次幂 E2

如果这对您更有意义。 &gt;&gt;1 与除以 2、&gt;&gt;2 除以 4、&gt;&gt;3 除以 8 等相同。在正值的二进制表示中,除以 2 相当于将所有位向右移动一位,丢弃最小的位,并用 0 填充最大的位。

“如果左侧操作数有符号,则右移可能是也可能不是逻辑移位(即行为未定义)。”

第一部分是正确的(它可能是也可能不是逻辑转变——它在某些编译器/平台上,但不是在其他编译器/平台上。我认为到目前为止最常见的行为是它不是)。第二部分是假的,行为不是未定义的。未定义的行为意味着任何事情都可以发生——崩溃、恶魔从你的鼻子里飞出、随机值等等。标准不在乎。 C++ 标准规定行为未定义的情况很多,但这不是其中之一。

事实上,如果左边的操作数是有符号的,并且值为正,那么它的行为与无符号移位相同。

如果左边的操作数是有符号的,并且值为负,那么结果值是实现定义的。不允许坠毁或着火。实施必须产生结果,实施文档必须包含足够的信息来定义结果。在实践中,“实现文档”从编译器文档开始,但这可能会隐式或显式地将您引向操作系统和/或 CPU 的其他文档。

再次从标准,5.8/3:

如果E1 有带符号类型和负数 值,结果值为 实现定义。

【讨论】:

  • @Mark:我已经帮你搞定了。 :) ...废话!我想为此 +1,但我将它用于 Mark!
  • @GMan:我为你 +1。现在有人要替我掩护了。
  • 如果它能让任何人感觉更好,我已经达到了这一天的上限 :-)
  • 哦,天哪。 :) @James:感谢您无私且显然是愚蠢的牺牲。 :)
【解决方案2】:

我假设您知道转移意味着什么。假设您正在处理 8 位 chars

unsigned char c;
c >> 9;
c >> 4;
signed char c;
c >> 4;

第一次移位,编译器可以随意做任何事情,因为 9 > 8 [char 中的位数]。未定义的行为意味着所有的赌注都没有了,没有办法知道会发生什么。第二个转变定义明确。你在左边得到 0:11111111 变为 00001111。第三个转变和第一个转变一样,是未定义的。

请注意,在第三种情况下,c 的值是什么并不重要。当它引用signed时,它表示变量的类型,而不是实际值是否大于零。 signed char c = 5signed char c = -5 都已签名,向右移动是未定义的行为。

【讨论】:

  • 请注意,无论这种语言是什么,它都不是 C++。这直接与标准相矛盾。
  • 除了在代码示例中重复使用c 之外,这确实是引用的段落所说的(和意思)。所以 +1 一个。 -1 因为没有注意到引用的段落是一堆旧垃圾;-)
  • 这段不正确的事实让我大吃一惊。这绝对是我记得的行为;我想知道我是否在某个时候读过同一本书并且从那以后一直生活在谎言中?
  • 请放心,多年阅读一本书并带着一个错误的信念只会发生在您身上,而不会发生在我们其他 60 亿人身上。你是独一无二的。
  • @David:我知道,这就是我如此沮丧的原因。
【解决方案3】:

如果移位运算符后的值大于左侧操作数的位数,则结果未定义。

意思是(unsigned int)x &gt;&gt; 33can do anything[1]

如果左侧操作数是无符号的,则右移是逻辑移位,因此高位将用零填充。

这意味着0xFFFFFFFFu &gt;&gt; 4必须是0x0FFFFFFFu

如果左侧操作数有符号,则右移可能是也可能不是逻辑移位(即行为未定义)。

这意味着0xFFFFFFFF &gt;&gt; 4 可以是0xFFFFFFFF(算术移位)或0x0FFFFFFF(逻辑移位)或物理定律允许的任何内容,即结果未定义。

[1]:在 32 位机器上使用 32 位 int

【讨论】:

  • “可以是任何东西”并不完全正确——更准确的说法是“执行(int)x &gt;&gt; 33 可以做任何事情”,包括将您的程序置于不正确执行无关代码的状态。此外,在第三种情况下,如果说行为是“未定义的”而不是“实现定义的”,那么它也可以做任何事情。
  • 标准没有规定UB的结果必须遵循物理规律。
  • 在 C++ 中,过度移位的结果是未定义的,实际上是负数的有符号整数类型的值右移是实现定义的(尽管定义了位模式),所有else 定义明确。
【解决方案4】:

如果移位运算符后面的值 大于中的位数 左边的操作数,结果是 未定义。

如果您尝试将 32 位整数移位 33,则结果未定义。即,它可能全为零,也可能不全为零。

如果左边的操作数是无符号的, 右移是逻辑移位,所以 高位将被填充 零。

无符号数据类型在右移时将用零填充。

所以1100 &gt;&gt; 1 == 0110

如果左边的操作数有符号, 右移可能是也可能不是 逻辑转变(即行为 未定义)。

如果数据类型是有符号的,则行为未定义。有符号数据类型以特殊格式存储,其中最左边的位表示正数或负数。因此,在有符号整数上移动可能无法达到您的预期。有关详细信息,请参阅 Wikipedia 文章。

http://en.wikipedia.org/wiki/Logical_shift

【讨论】:

  • 这是对签名数据类型的一种非常重要且相当误导的简化。
  • @Dennis:我会使用比“相当误导”更强烈的短语,因为有些陈述是错误的。
  • 我只是在解释引号在说什么,因为这就是 OP 所要求的。据我所知,他已经知道规格是什么,并试图确定作者是否正确。毕竟,OP 是一个代码大师。 ;) 不管怎样,我知道有人会记住所有的 C++ 标准语言规范,会停下来为他反刍整个事情。
【解决方案5】:

为了给出一些上下文,这是该段的开头:

移位运算符也操作位。左移运算符 (>) 生成运算符左侧的操作数,该运算符右移运算符后指定的位数。

现在剩下的,有解释:

如果移位运算符后的值大于左侧操作数的位数,则结果未定义。

如果您有一个 32 位整数并且您尝试移位 33 位,则这是不允许的,并且结果未定义。换句话说,结果可能是任何东西,或者您的程序可能会崩溃。

如果左侧操作数是无符号的,则右移是逻辑移位,因此高位将用零填充。

这表示当 a 是无符号整数时,它被定义为写 a &gt;&gt; b。右移时,最低有效位被移除,其他位被下移,最高有效位变为零。

换句话说:

这个:110101000101010 >> 1 变为:011010100010101

如果左侧操作数有符号,则右移可能是也可能不是逻辑移位(即行为未定义)。

实际上,我认为这里的行为是在a 为负数时定义的实现,并在a 为正数时定义,而不是引用中建议的未定义。这意味着如果您在a 是负整数时执行a &gt;&gt; b,则可能会发生许多不同的事情。要查看您得到的内容,您应该阅读编译器的文档。一个常见的实现是,如果数字为正,则移入零,如果数字为负,则移入零,但如果您希望编写可移植代码,则不应依赖此行为。

【讨论】:

  • 如果左边的操作数是有符号的,那么它要么是非负的(结果是定义的)要么是负的(结果值是实现定义的)。
  • @David:是的,我知道在阅读史蒂夫的回答后就知道了,但无论如何谢谢。我会更新帖子。 :)
【解决方案6】:

我想关键词是“未定义”,这意味着规范没有说明应该发生什么。大多数编译器在这种情况下会做一些明智的事情,但你通常不能依赖任何行为。通常最好避免调用未定义的行为,除非您使用的编译器的文档说明了它在特定情况下的作用。

如果您尝试将 32 位值移位超过 32 位,则第一句表示它是未定义的。

第二个说如果你将一个无符号整数右移,左边的位将被零填充。

第三个说如果你将有符号的 int 向右移动,它没有定义将放在左边的位中。

【讨论】:

  • 请注意,在第三个中,它表示 行为 未定义,而不仅仅是值。
猜你喜欢
  • 2017-07-12
  • 2012-04-09
  • 1970-01-01
  • 2015-10-24
  • 2012-12-29
  • 1970-01-01
  • 1970-01-01
  • 2010-09-05
  • 2014-11-17
相关资源
最近更新 更多