当前位置: 首页 > 知识库问答 >
问题:

编译器优化可能会导致整数溢出。可以吗?

贺善
2023-03-14

我有一个整数 x。为简单起见,假设 ints 占据范围 -2^31 到 2^31-1。我想计算2 * x-1。我允许 x 是任何值 0

一种解决方案是计算< code>2*(x-1) 1。比我想要的多了一个减法,但是这个不应该溢出来。但是,编译器会将其优化为< code>2*x-1。这是源代码的问题吗?这是可执行文件的问题吗?

以下是 2*x-1 的弩线输出:

func(int):                               # @func(int)
        lea     eax, [rdi + rdi]
        dec     eax
        ret

以下是 2*(x-1) 1 的闩线输出:

func(int):                               # @func(int)
        lea     eax, [rdi + rdi]
        dec     eax
        ret

共有3个答案

罗河
2023-03-14

ISO C规则适用于您的源代码(总是适用,不管目标机器是什么)。而不是编译器选择生成的asm,尤其是对于有符号整数包装只起作用的目标。

“as-if”规则要求函数的asm实现对于抽象机没有遇到有符号整数溢出(或其他未定义的行为)的每个输入值产生与C抽象机相同的结果。asm如何产生这些结果并不重要,这就是“仿佛”规则的全部要点。在某些情况下,如您的情况,最有效的实现将对抽象机器无法包装的某些值进行包装和展开,反之亦然。

有符号整数溢出在 C 抽象机中为 UB 的一个影响是,它允许编译器优化 int 循环计数器以指示指针宽度,而不是每次通过循环或类似操作时都重做符号扩展。此外,编译器还可以推断值范围限制。但这与他们如何将逻辑实现到某些目标机器的asm中是完全分开的。UB并不意味着“需要失败”,实际上恰恰相反,除非你用-fsanitize=undefined编译。对于优化器来说,如果您用比ISO C实际给出的更多的保证来解释源代码,那么优化程序可以使asm与源代码不匹配(加上实现超出此范围的任何保证,例如如果您使用gcc -fwrapv。)

对于像< code>x/2这样的表达式,每个可能的< code>int x都有明确定义的行为。对于< code>2*x,编译器可以假设< code>x

2*(x-1) 1 表示合法值范围 x2*x-1 的合法值范围相差 1。它们为每个x产生相同的结果,这是它们两个都明确定义的输入,因此如果编译器没有为两者提供相同的有效asm,那么它将做得很糟糕。

延伸阅读:

  • 是否有一些有意义的统计数据来证明保持有符号整数算术溢出未定义是合理的?re:它允许的一些有用的循环优化。
  • http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
  • 未定义的行为是否适用于ASM代码?(否)
  • 内联x86程序集中的整数溢出是未定义的吗?

整个情况基本上是一团糟,C语言的设计者没有预料到当前优化编译器的复杂性。像Rust这样的语言更适合它:如果你想要包装,你可以(而且必须)告诉编译器关于它的每个操作,无论是有符号类型还是无符号类型。例如x.wrapping_add(1)

曹兴贤
2023-03-14

仅仅因为有符号整数溢出在C语言级别没有很好的定义(直到C 20 ),并不意味着在汇编级别也是如此。由编译器来发出在目标CPU架构上定义良好的汇编代码。

我很确定本世纪制造的每个CPU都使用了2的补码有符号整数,溢出是为这些整数很好地定义的。这意味着简单地计算2*x,让结果溢出,然后减去1,让结果下限溢位是没有问题的。

存在许多这样的C语言级规则来掩盖不同的CPU架构。在这种情况下,有符号整数溢出未定义,以便针对使用一个人的赞美或有符号整数的符号/大小表示的CPU的编译器不会被迫添加额外的html" target="_blank">指令来符合两个赞美的溢出行为。

但是,不要假设您可以使用在目标CPU上定义良好但在C中未定义的构造,并得到您期望的答案。C编译器假定在执行优化时不会发生未定义的行为,因此如果代码不是定义良好的C,它们可以并且将发出与您期望的不同的代码。

柯建修
2023-03-14

正如Miles所暗示的:C代码文本受C语言规则的约束(integer overflow=bad),但编译器仅受cpu规则的约束(overflow=ok)。允许进行代码不允许的优化。

但不要以此为借口偷懒。如果你写了未定义的行为,编译器会认为这是一个提示,并进行其他优化,导致你的程序做了错误的事情。

 类似资料:
  • 问题内容: 假设我在C代码中有类似的内容。我知道您可以使用a 代替,以使编译器不对其进行编译,但是出于好奇,我问编译器是否也可以解决此问题。 我认为这对于Java编译器来说更为重要,因为它不支持。 问题答案: 在Java中,if内的代码甚至都不是已编译代码的一部分。它必须编译,但不会写入已编译的字节码。它实际上取决于编译器,但我不知道没有对它进行优化的编译器。规则在JLS中定义: 优化的编译器可能

  • 问题内容: 在回答了这个问题之后,我很困惑为什么这段代码中的整数溢出而不是负数。奇怪,为什么这么精确的数字?为什么是0? 输出: 问题答案: 仅当的起始值为偶数时,才会发生这种情况。 根据JLS§15.17.1: 如果整数乘法溢出,则结果是数学乘积 的低阶位 ,以某种足够大的二进制补码格式表示。结果,如果发生溢出,则结果的符号可能与两个操作数值的数学积的符号不同。 如果我们以二进制格式而不是十进制

  • 我经常遇到这种情况。乍一看,我认为,“这是糟糕的编码;我正在执行一个方法两次,必然会得到相同的结果。”但想到这里,我不得不怀疑编译器是否像我一样聪明,并能得出相同的结论。 编译器的行为是否取决于 方法的内容?假设它看起来像这样(有点类似于我现在的真实代码): 除非对这些对象来自的任何存储进行处理不当的异步更改,否则如果连续运行两次,肯定会返回相同的内容。但是,如果它看起来像这样(为了论证而无意义的

  • Nicolai Josuttis在其著作《C标准库(第二版)》中指出,与普通函数相比,编译器可以更好地优化lambdas。 此外,C 编译器优化 lambda 的效果比普通函数更好。(第213页) 这是为什么呢? 我想当涉及到内联时,应该不会再有任何区别了。我能想到的唯一原因是编译器可能有更好的lambdas本地上下文,这样可以做出更多假设并执行更多优化。

  • 问题内容: Java编译器会优化简单的重复数学运算,例如: 我知道我可以将结果分配给if语句之前的变量,然后返回变量,但这有点麻烦。如果编译器自动识别出正在执行相同的计算,并将结果自己缓存到临时变量中,我宁愿遵守上述约定。 *编辑-我是个白痴。我试图简单/过多地提出我的问题。它不是简单的:if(x> y) 问题答案: 答案是肯定的。这称为“ 公共子表达式消除”,它是Java,C / C ++和其他

  • 在 C 或 C 中,如果编译器遇到一个 循环,其中计数器从 计数到 n, n 是一个变量(不是函数调用,也不是常量),编译器是否会通过检查变量 (绑定变量)是否会在循环期间更改(访问写入, 例如: 可以是循环前计算的字符串的长度),通过优化这里,我的意思是将其值复制到寄存器以避免内存访问? 下面是一个示例: 编译器会注意到这一点并对其进行优化吗?