问题 在C ++ 11中左移(


左移是负面的 int C ++ 11中未定义的行为?

这里的相关标准段落来自5.8:

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

令我困惑的部分是:

否则,如果E1具有有符号类型和非负值,则E1×2E2   可以在结果类型中表示,那么这就是结果值;   否则,行为未定义。

这应该被解释为意味着左移 任何 负数是UB?或者它只是意味着什么 如果 你LS为负数,结果不适合结果类型, 然后 这是UB?

而且,前一条款说:

1 /移位运算符<<和>>组从左到右。       移位表达:           添加剂的表达           shift-expression << additive-expression           shift-expression >> additive-expression

操作数应为整数或无范围的枚举类型   进行整体促销。

结果的类型是提升的左操作数的类型。该   如果右操作数为负数或更大,则行为未定义   大于或等于提升左操作数的位长。

这使得明确使用负数  操作数是UB。如果UB对另一个操作数使用负数,我希望在这里也要明确。

所以,底线是:

-1 << 1

未定义的行为?


@Angew 提供 对标准的一种psudocode解释简洁地表达了一种可能的(可能的)有效解释。其他人质疑这个问题是否真的与“行为未定义”的语言相对于我们(StackOverflow)使用“未定义行为”这一短语的适用性。这个编辑是为了提供一些我想要问的更多说明。

@Angew对Standardese的解释是:

if (typeof(E1) == unsigned integral)
  value = E1 * 2^E2 % blah blah;
else if (typeof(E1) == signed integral && E1 >= 0 && representable(E1 * 2^E2))
  value = E1 * 2^E2;
else
  value = undefined;

这个问题真正归结为这个 - 实际上是正确的解释:

value = E1 left-shift-by (E2)

switch (typeof(E1))
{
case unsigned integral :
  value = E1 * 2^E2 % blah blah;
  break;

case signed integral :
  if (E1 >= 0)
  { 
    if (representable(E1 * 2^E2))
    {
      value = E1 * 2^E2;
    }
    else
    {
      value = undefined;
    }
  }
  break;
}

Sidenote,从psudocode的角度来看这个问题让我很清楚@Agnew的解释是正确的。


12631
2017-10-25 15:31


起源

我知道结果值可能是未定义的,因为标准没有明确设置负数的表示。但这不应该意味着操作的结果是不确定的吗?我不确定“行为未定义”完全等同于术语“未定义行为”。我更倾向于认为作者意味着结果值是未定义的(但无论哪种方式都应该加强语言)。 - Martin York
@LokiAstari标准中已有术语,用于说明何时定义行为但不需要特定值;它会说这个值是未指定的或不确定的。如果这是意图,我希望使用该语言。 - bames53
只要我们是语言律师......在C ++ 11之后,CWG 1457修改了p2,如下所示: open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1457 。这种调整的影响是,可以将1位有符号类型转换为符号位,而后C ++ 11则现在不是未定义的行为。这样做是为了人们可以(例如) 1 << 31 要得到 0x80000000 对于一个没有UB神的32位int来统治你的头。这对于作者来说是一种解脱 std::numeric_limits<int>::min() 通常以这种方式计算,也是 constexpr,意思是UB在编译时被捕获。 - Howard Hinnant
@HowardHinnant FYI,我在回答这个问题时引用你的评论 题。 - Shafik Yaghmour
谢谢你的属性! - Howard Hinnant


答案:


是的,我会说这是未定义的。如果我们将标准转换为伪代码:

if (typeof(E1) == unsigned integral)
  value = E1 * 2^E2 % blah blah;
else if (typeof(E1) == signed integral && E1 >= 0 && representable(E1 * 2^E2))
  value = E1 * 2^E2;
else
  value = undefined;

我会说他们明确右手操作数而不是左手操作数的原因是你引用的paragrpah(带有右手操作数的那个)适用于左右移位。

对于左手操作数,裁决有所不同。左移一个负数是未定义的,右移它是实现定义的。


8
2017-10-25 15:46



+1我明白你在说什么。有关他们为什么只是实施定义的任何想法?也就是说,为什么要避免将负片转移到丢失的玩具的土地上呢? - John Dibling
@JohnDibling不知道,真的。对于处理器架构专家来说,这是一个问题,我对此知之甚少。这不是关于位移的唯一不对称。 Java有 >>> 但没有 <<<, 例如。 - Angew
@Angew: <<< 和 << 是相同的操作,所以没有意义。但 >>> 和 >> 是两种不同的操作。 - Puppy
@DeadMG嗯,从技术上讲,你可以“向左移动,但保持标志位不变。” - Angew
@Mike:从我读到的内容来看,行为最初是未定义的,因为在这种情况下可能会有某个实现触发陷阱的实现。在几十年没有找到任何这样的实现之后,它将被改变为实现定义,除了这样做将不再允许编译器将任何导致负数左移的事件链视为不可能。 - supercat


这应该被解释为左移任何负数是UB吗?

是的,当给出任何负数时,行为是不确定的。仅当满足以下两个条件时才会定义行为:

  • 这个数字是非负数
  • E1×2E2 可在结果类型中表示

这实际上是“如果E1具有有符号类型和非负值,并且E1×2E2 可以在结果类型中表示,那么这就是结果值;否则,行为是不确定的,“说:

if X and Y
  then Z
else U

5
2017-10-25 18:34





根据问题回答:

问题确实是:

我们可以将术语“行为未定义”等同于术语“未定义行为”。

因为它目前的措辞意味着“未定义的行为”。

关于这种情况的个人评论

但我不相信这是作者的意图。
如果这是作者的意图那么我们应该有一个说明解释原因。但是我更倾向于认为作者意味着该操作的结果是未定义的,因为标准没有明确定义负数的表示。如果没有为负数明确定义负数的表示,那么移动位将导致未定义的值。

无论哪种方式,措辞(或解释)都需要收紧/扩大,以使其不那么模糊。


1
2017-10-25 17:48



@Mysticial:记住UB意味着包括理性在内的任何事情都可能发生。 - Martin York
“行为未定义”是标准如何描述某些东西调用UB。只计算“行为未定义”的命中,真的。 - Xeo
使其未定义而非实现定义的一个正当理由是,某些转换的编译时评估会对这些相同转换的运行时评估产生不同的结果。使其实现定义将迫使实现选择,并且选择意味着编译器的行为必须改变,以获得错误程序的唯一好处。
+1,但这不是我的问题所在。我很清楚,当标准说“行为未定义”时,它们意味着当我们在计算方法或结果值中说“未定义行为”时,我们(StackOverflow)的含义。毕竟,如果计算未定义,那么必须是结果值。根据定义,在UB操作之后发生的所有事情本身就是UB操作。 - John Dibling
考虑以下代码: int foo(int x) { int r = x << 20; if (x < -1000000) return -1; return 1; }  编译器可以自由地假设负数的左移是UB,因此永远不会发生,因此if语句可以被忽略,整个例程变成简单的“返回1”。 clang的未来版本将开始进行此优化。欢闹可能随之而来...... - jorgbrown