当循环变量在溢出时未定义时,可以进行哪些优化?

Enr*_*lis 2 c integer-overflow compiler-optimization undefined-behavior

本文提供了一个 C 代码示例片段,由于循环计数器类型未定义溢出,因此编译器可以对其进行优化。

这是带有评论的片段

for (i = 0; i <= N; ++i) { ... }
Run Code Online (Sandbox Code Playgroud)

在此循环中,如果“i”在溢出时未定义,编译器可以假设循环将精确迭代 N+1 次,这允许进行广泛的循环优化。另一方面,如果变量定义为溢出时回绕,那么编译器必须假设循环可能是无限的(如果 N 为 INT_MAX,就会发生这种情况)——然后禁用这些重要的循环优化。这尤其影响 64 位平台,因为大量代码使用“int”作为归纳变量。

我有不少疑问:

  • 我知道,如果 的类型i在溢出时未定义,编译器可以假设i不会回绕,但是为什么这意味着循环运行了N+1多次?不能i在循环体中改变它吗?
  • 即使考虑到这一假设,它还能基于此进行哪些优化?例如,如果N在编译时未知,则无法展开循环,不是吗?
  • “广泛的循环优化”让我觉得我在这里错过了很多。

Eri*_*hil 6

我知道,如果 的类型i在溢出时未定义,编译器可以假设i不会回绕,但是为什么这意味着循环运行了N+1多次?不能i在循环体中改变它吗?

首先回答第二个问题,作者以他们的例子为例,i循环中没有改变,并且循环不包含break会终止循环的代码或其他代码。这只是一个简短的示例,面向知识渊博的受众,希望他们熟悉这些内容并填补空白。

回答第一个问题,不会换行的事实i并不意味着循环会运行N+1多次。这段话并没有这么说,它说它允许编译器假设这一点。道理是这样的:

  • 如果永远不会溢出,N++i循环恰好运行N+1一次。
  • 如果在某个点N溢出++i,则该行为未定义,并且语言标准允许任何行为。如果程序崩溃,则符合标准。如果N+1换行并且测试i <= N始终为真,并且循环永远迭代,则符合标准。如果循环运行了N+1多次,则符合标准。作为编译器实现者,我们可以选择这些行为中的任何一个,并且生成的行为将符合语言标准。

如果有自由选择,在这种情况下,我们选择表现得好像循环总是运行N+1多次,因为这是一个简化的假设:它为我们提供了针对非溢出情况和溢出情况的相同规范。

  • 即使考虑到这一假设,它还能基于此进行哪些优化?例如,如果N在编译时未知,则无法展开循环,不是吗?

如果循环是for (int i = 0; i <= N; ++i) { sum += i; },我们可以优化为sum += N*(N+1)/2;(适当考虑可能的溢出来计算)并完全删除循环。如果我们需要表现得像i溢出时被包裹一样,我们就无法完全删除循环;我们需要一个测试来看看 的值是否N使得循环退出或永远继续下去。(尽管标准中关于循环取得进展的其他语言;这是一个简化的示例。)

  • “广泛的循环优化”让我觉得我在这里错过了很多。

这是一个广泛的主张,没有列出几个,所以不要太担心这部分。