【问题标题】:Integer overflow and undefined behavior整数溢出和未定义行为
【发布时间】:2010-10-17 00:28:39
【问题描述】:

由于可能undefined behavior,在实际加/减之前检测整数溢出有很多问题。所以,我的问题是

为什么它首先会产生这个undefined behavior

我能想到两个原因:

1) 在这种情况下产生异常的处理器。当然,它可以被关闭,而且很可能一个写得很好的 CRT 可以做到这一点。

2) 使用其他二进制数字表示的处理器(1 的补码?以 10 为底?)。在这种情况下,未定义的行为将表现为不同的结果(但不会崩溃!)。好吧,我们可以忍受。

那么,为什么有人要避免引起它呢?我错过了什么吗?

【问题讨论】:

标签: c


【解决方案1】:

虽然有符号溢出被指定为未定义行为的历史原因可能是这些虚假的遗留表示(补码/符号大小)和溢出中断,但它保持未定义行为的现代原因是优化。正如 J-16 SDiZ 所暗示的那样,有符号溢出是未定义行为这一事实允许编译器优化其代数真值(但不一定是表示级真值)已由前一个分支建立的一些条件。如果子表达式包含溢出,它还可以允许编译器以代数方式简化某些表达式(尤其是那些涉及乘法或除法的表达式),从而产生与最初编写的求值顺序不同的结果,因为允许编译器假设溢出你给它的操作数不会发生。

为了允许优化而未定义行为的另一个重要例子是别名规则。

【讨论】:

  • 有人可能会争辩说,无符号整数应该表现一致,因为为什么不假设优化器中的无符号整数 a + b >= a 和 a + b >= b (类似于得出 a + b >在 J-16 SDiZ 的带符号整数示例中,a > 0 和 b > 0 为 0)?在我看来,这在 C 规范中更奇怪。
  • @nucleon:无符号类型被指定为模算术(其中不等式的代数等价不成立)。签名类型不是。如果你喜欢在 C 中称其为怪异,那就这样吧,但事实就是如此,这并不是你可以改变的。
【解决方案2】:

虽然大多数现代 CPU 使用 2 的补码,并且整数溢出会导致可预测的模回绕,但这绝不是通用的 - 以保持语言足够通用以使其可以在最广泛的范围内使用架构最好指定整数溢出为 UB。

【讨论】:

  • 如果您的编译器只是忽略生成无用的有符号算术操作码并将无符号操作码用于有符号和无符号类型,则任何 cpu 都是二进制补码。在进行比较时,这可能会导致轻微的性能损失(例如,现在 x<-1 需要 2 次机器级比较而不是 1 次),但我仍然更喜欢这种方式。
  • @R: 2 的补码并不是唯一的问题 - 例如一些 CPU(例如 DSP)具有饱和算法而不是模算法。
  • 无论如何支持无符号类型都需要模块化算法......如果你的机器没有它,我想你必须实现它。一种方法可能是使用最高位作为填充,并在每次操作后将其归零。
  • 不仅仅是不同的机器;它是同一台机器上同一编译器内的不同优化级别:kuliniewicz.org/blog/archives/2011/06/11/…
  • @R.. 你可以保留一个符号位。您不必使用模运算来支持有符号或无符号值 - 就我个人而言,我在进行自己的计算时不使用模运算并且支持无符号计算。
【解决方案3】:

规范中的undefined behavior 位涉及一些编译器优化。例如:

if (a > 0 && b > 0) {
    if ( a + b <= 0 ) {
       // this branch may be optimized out by compiler
    } else {
       // this branch will always run
    }
}

现代 C 编译器并不是那么简单,它会进行大量的猜测和优化。

【讨论】:

  • 好点,但这仅意味着 C 编译器不会帮助您检测它。
  • 另一点:虽然编译器能够检测到这个算术条件,但一些位操作可以欺骗它,这样条件就不会被优化。此外,正如 nategoose 所建议的,我们可以将总和声明为 volatile
  • 这不是volatile 关键字的有效使用。如果它有效,它是特定实现的结果,而不是特定行为。 IE。它仍然是未定义的行为。
  • 如果指定运算结果产生的值超出int 的范围,则可以实现此类优化而不需要整数溢出为未定义行为具有算术正确的结果 mod (2*INT_MAX+2),并且如果将这样的值存储到 int 变量中,则对该变量的任何读取都可以独立地表现为任何具有相同一致性的任意数学整数班级。这样的规则将允许许多有用的优化,并且也适合常见的执行模型......
  • ...在寄存器中保存的值可能比int 长。事实上,如果我有我的 druthers,我会为这些值定义一个标准术语“部分不确定”,并指定一个实现可以将超出范围的有符号整数向下转换的行为定义为产生一个部分不确定的值。否则,当与 int 大小不符合预期的机器一起使用时,uint32_t 之类的类型很容易产生 UB。
【解决方案4】:

我认为您的假设 1) 如果我的记忆正确的话,至少在一个重要的历史架构 CDC 上,可以为任何给定处理器关闭此功能是错误的。

【讨论】:

  • 这个真的很老了。我宁愿用#ifdef 处理这些情况。
  • @ruslik:问题不在于 如何 处理这个问题,#ifdef 只是避免 UB 的一种方法。但是你看,正如你自己所说,你会以某种方式处理这个问题。
  • 向后兼容性有其局限性。在这种情况下,我宁愿编写错误但简单的代码。
  • @ruslik:确保您的里程可能会有所不同。当然取决于您是自己编写代码还是希望它最终出现在库中。我发现我们在 32 位与 64 位可移植性方面经历过(并且仍在继续)所有这些大惊小怪,这对整个行业来说都是一种耻辱。我们将在下一步再次看到这一点。所以,不,我为自己努力坚持标准。
  • 可以关闭捕获的假设是正确的,因为作为编译器的实现者,您可以简单地选择从不生成有符号算术操作码并始终使用干净/安全的无符号操作码。然后你可以同时免费获得补码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多