【问题标题】:Why is C++ underflow/overflow behaviour considered undefined?为什么 C++ 下溢/上溢行为被认为是未定义的?
【发布时间】:2014-11-16 12:05:48
【问题描述】:

我了解整数下溢和上溢是未定义的。

但是,鉴于 C++ 最终会编译为程序集,实际上是否定义了行为?

按位表示保持不变,整数格式保持不变 0111..11 将始终翻转到 1000..00,下溢也相同,那么为什么不将其视为已定义行为?

关于程序集编译,我是从我们在学校教过的基本程序集派生的,但是代码块给出了

int x = INT_MAX;
int y = x+1;

编译成

00401326    movl   $0x7fffffff,0x8(%esp)
0040132E    mov    0x8(%esp),%eax
00401332    inc    %eax
00401333    mov    %eax,0xc(%esp)

现在,不管 x 的值是多少,不会总是有 inc 或 add 指令吗?那么,未定义的行为是从哪里出现的呢?

【问题讨论】:

  • 这个问题很有趣,但我认为您应该添加两种情况的 C++ 编码示例,以及编译器为每种情况生成的反汇编。
  • 我必须同意@barakmanos
  • 在 C++ 中是未定义的,因为世界上各种 CPU 没有就定义达成一致。例如,一些 CPU 使用“饱和”数学,溢出导致最大值。
  • 它是为执行它的特定硬件定义的,但不是由 C++ 标准定义的。
  • 如果编译器可以假设,某事是未定义的行为,这是出于某种原因。在大多数情况下,它是性能。编译器可以采用的假设越多,它可以生成的代码就越优化。而且您仍然可以对无符号整数执行“标准”操作。

标签: c++


【解决方案1】:

但是,鉴于 C++ 最终编译为汇编,行为实际上没有定义吗?

不,因为编译器决定它发出什么样的程序集。如果编译器愿意,它可以生成程序集,在遇到未定义的行为时擦除您的硬盘。

(实际上,“C++ 最终编译为程序集”甚至可能不是真的。存在C++ interpreters, for example - 标准没有指定 C++ 应该如何/编译成什么格式。

标准的创建者决定不对其进行定义的原因之一——几乎总是——优化的机会。例如,如果有符号溢出是 UB,那么编译器可以假设 x + 1 > x 始终为真,并根据此前提条件生成更简单/更短/更快的代码。

【讨论】:

    【解决方案2】:

    有符号整数的溢出在 C++ 标准中未定义,正是因为不同的编译器、汇编器和平台可能会以不同的方式解释它们。

    当您知道程序要运行的平台时,您可以推断程序的行为,但如果没有这些知识,就不可能预测它的行为方式。

    按位表示保持不变,整数格式保持不变

    这根本不一定是真的。

    【讨论】:

    • 谢谢,你能分享一下按位表示不会导致 intmax + 1 = intmin 和 intmin-1=intmax 的情况吗?
    • @user87166 当编译器优化它时,因为它总是 UB。
    • 如果原因是平台依赖,为什么明确定义了无符号溢出?
    • @KarolyHorvath 可能是因为它引起的兼容性问题较少——我真的无法想象一个平台使用不同于全零和最大值全零的东西。不过,存储签名位可能更随意。
    【解决方案3】:

    IIRC,这是未定义的原因是因为 C++ 没有规定目标机器需要如何存储数字。

    假设每个字节有 8 位/char。这会给我们:

    • std::numeric_limits<char>::max()
      • 2 的补码:127 (0b01111111)
      • 1 的补码:127 (0b01111111)
      • 有符号震级:127 (0b01111111)
    • std::numeric_limits<char>::min()
      • 2 的补码:-128 (0b10000000)
      • 1 的补码:-127 (0b10000000)
      • 有符号震级:-127 (0b11111111)

    您已经可以看到,我们有不同的位模式和最小值,而最大值是相同的。

    那么,如果将最大值加 1 会发生什么?假设我们转换为无符号,加 1,然后转换回有符号。结果是:

    • 2 的补码:-128 (0b10000000)
    • 1 的补码:-127 (0b10000000)
    • 有符号幅度:-0 (0b10000000)

    一团糟。但是如果我们想让溢出定义明确,我们能做什么呢?假设我们有一个signed char c = 127; 并且想要加 1。我们可以定义结果应该始终为 -127,因为这是所有三个引用系统都可以表示的(忽略这些不是唯一表示有符号整数的系统)。但这意味着编译器必须专门捕获溢出并在 2 的补码(大多数系统)和有符号幅度系统上正确处理它,这意味着额外的指令,因此这些机器上的性能不太理想。

    在现实生活中你不太可能遇到不使用 2 的补码的机器,所以 C++ 人不能简单地强制它吗?我没有发现任何当前 CPU 或 DSP 使用除 2 的补码以外的任何东西,但是当 C++ 被创建时 机器使用 1 的补码(例如 CDC Cyber ) 并且听到一些 DSP 今天仍然在做我不会感到惊讶(毕竟,有些 DSP 有char sizes other than 8 bit)。这就是它保持未定义行为的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-15
      • 1970-01-01
      • 1970-01-01
      • 2015-08-13
      • 1970-01-01
      • 2017-02-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多