【问题标题】:Why was 1 << 31 changed to be implementation-defined in C++14?为什么 1 << 31 在 C++14 中改为实现定义?
【发布时间】:2014-12-07 11:44:46
【问题描述】:

在 2014 年之前的所有 C 和 C++ 版本中,写作

1 << (CHAR_BIT * sizeof(int) - 1)

导致未定义的行为,因为左移被定义为等同于连续乘以2,并且这种移位导致有符号整数溢出:

E1 &lt;&lt; E2 的结果是E1 左移E2 位位置;空出的位用零填充。 [...] 如果E1 具有带符号类型和非负值,并且E1 × 2E2 在结果类型中是可表示的,那么这就是结果值;否则,行为未定义

但在 C++14 中,&lt;&lt; 的文本已更改,但乘法却没有:

E1 &lt;&lt; E2的值是E1左移E2位位置;空出的位用零填充。 [...] 否则,如果 E1 具有带符号类型和非负值,并且 E1 × 2E2 可以表示为 对应的无符号类型结果类型,然后那个值,转换为结果类型,就是结果值;否则,行为未定义。

现在的行为与对有符号类型的超出范围分配相同,即由 [conv.integral]/3 涵盖:

如果目标类型是有符号的,如果它可以在目标类型(和位域宽度)中表示,则值不变;否则,值是实现定义的

这意味着写1 &lt;&lt; 31 仍然是不可移植的(在具有 32 位 int 的系统上)。那么为什么要在 C++14 中做出这种改变呢?

【问题讨论】:

  • +1 Howard Hinnant comments on this topic here,我记得这条评论的唯一原因是这条评论是我对 question 的灵感的一部分。
  • 想象一下,如果他们只是简单地陈述了以下内容:“约束:提升后,左操作数 E1 应为无符号整数类型”。它将人类从数量惊人的与转变相关的微妙错误中拯救出来。

标签: c++ bit-shift c++14


【解决方案1】:

相关问题是CWG 1457,其中的理由是更改允许1 &lt;&lt; 31 用于常量表达式:

5.8 [expr.shift] 第 2 段的当前措辞使其未定义 通过以下方式创建给定类型的最负整数的行为 将(有符号的)1 左移到符号位,即使这不是 罕见地完成并且在大多数情况下都能正常工作 (双补)架构:

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

因此,这种技术 不能在常量表达式中使用,这会破坏 大量的代码。

常量表达式不能包含未定义的行为,这意味着在需要常量表达式的上下文中使用包含 UB 的表达式会使程序格式错误。 libstdc++ 的numeric_limits::min,例如once failed to compile in clang 就是因为这个原因。

【讨论】:

  • 该措辞的作者似乎很困惑......新规则不允许“通过将(有符号)1左移到符号位来创建给定类型的最负整数“ 任何一个。结果是实现定义的,这远不能保证给出最负的值。
  • @BenVoigt 对于二进制补码,只有一个正常的结果。这在所有相关和常见的编译器中始终如一地实现。
  • @Loopunroller:问题中最后一个标准引用的措辞是这样的,以避免要求将无符号值转换为有符号值遵循二进制补码规则。此外,对于二进制补码只有一个合理的结果是不正确的。饱和算术也是一个有用的、理智的选择。甚至可以说这比将正值加倍创造负值更理智。
  • - max() - 1 真的很痛苦以至于他们决定改变标准吗?
  • +1 Fwiw,libc++ 用min() 计算max(),所以用max()min() 会非常有趣。 :-) 就我而言,底线是:我已经厌倦了站在我的头上来支持 any 现代 C++ 编译器不针对的架构。二进制补码有符号整数是我们现在和未来的现实。
猜你喜欢
  • 2013-05-28
  • 1970-01-01
  • 1970-01-01
  • 2014-11-29
  • 2017-03-05
  • 2016-03-26
  • 2023-03-21
  • 1970-01-01
  • 2012-07-17
相关资源
最近更新 更多