编译器是否允许消除无限循环?

可以优化编译器删除无限循环,它不会改变任何数据,比如

while(1) 
  /* noop */;

从分析编译器可以派生的数据流图中可以看出,这样的循环是“死代码”,没有任何副作用。

是否删除了C90 / C99标准禁止的无限循环?

C90或C99标准是否允许编译器删除这些循环?

更新:“微软C版本6.0基本上做了这种优化。”,请参阅由caf链接。

label: goto label;
return 0;

将被转化为

return 0;

C11在C11标准部分草案中澄清了这个问题的答案6.8.5迭代语句增加了以下段落:

一个迭代语句,其控制表达式不是一个常量表达式,156)不执行输入/输出操作,不访问易失性对象,并且在其主体中不执行同步或原子操作,控制表达式或(在for声明)其表达式-3,可以由实现来假定终止。157)

脚注157说:

这是为了允许编译器转换,例如,即使在无法证明终止的情况下也可以清除空循环。

所以你的具体例子:

while(1) 
  /* noop */;

是不公平的游戏优化,因为控制表达式是一个常量表达式。

作为UB的无限循环

那么为什么编译器允许优化无限循环(除了上面提供的例外),Hans Boehm提供了一个在无限循环中创建无限循环未定义行为的基本原理:下面的引用给出了对这个问题的良好感觉参与:

正如N1509正确指出的那样,当前草案基本上给出了6.8.5p6中无限循环的未定义行为。 这样做的一个主要问题是它允许代码跨越可能不终止的循环。 例如,假设我们有以下循环,其中count和count2是全局变量(或者已经取得了它们的地址),p是一个局部变量,其地址未被采用:

for (p = q; p != 0; p = p -> next) {
    ++count;
}
for (p = q; p != 0; p = p -> next) {
    ++count2;
}

这两个循环可以合并并替换为以下循环吗?

for (p = q; p != 0; p = p -> next) {
        ++count;
        ++count2;
}

如果没有6.8.5p6中无限循环的特殊配置,这将被禁止:如果第一个循环没有终止,因为q指向一个循环列表,则原始记录不会写入count2。 因此它可以与访问或更新count2的另一个线程并行运行。 尽管存在无限循环,但对于访问count2的转换版本来说,这已不再安全。 因此,转换可能会引入数据竞赛。

在这种情况下,编译器不太可能证明循环终止; 它必须理解q指向一个非循环列表,我认为这是非常多的主流编译器的能力,并且如果没有整个程序信息通常是不可能的。

C99

既然C99没有这样做,我们可以看看第5.1.2.3节中的as-if规则,它基本上说编译器只需模拟程序的可观察行为,要求如下:

对一致性实施的最低要求是:

  • 在顺序点上,易失性对象是稳定的,因为先前的访问已完成,并且后续的访问尚未发生。
  • 在程序终止时,写入文件的所有数据应与根据抽象语义执行程序所产生的结果相同。
  • 交互设备的输入和输出动态应按照7.19.3的规定进行。 这些要求的意图是尽可能快地出现无缓冲或线路缓冲输出,以确保提示消息实际上出现在等待输入的程序之前。
  • 严格阅读这个看起来会让实现优化一个无限循环。 我们当然可以想出如何优化一个无限循环会导致可观察行为的变化:

    while(1) ;
    printf( "hello worldn" ) ;
    

    许多人会认为,影响终止一个过程也是可观察的行为,这个位置是在C编译器反驳费马的最后一个定理:

    编译器在实现C程序时有很大的自由度,但是它的输出必须具有与标准中描述的“C抽象机器”所解释的相同的外部可见行为。 许多知识渊博的人(包括我)都这样认为,程序的终止行为不能改变。 很显然,一些编译器作者不同意,或者不相信它很重要。 有理性的人对解释不同意的事实似乎表明C标准是有缺陷的。

    更新

    我不知何故错过了上述文章“编译器和终止再访”的后续内容,其中说明了关于5.1.2.3节的以下内容:

    第二个要求是棘手的。 如果它正在讨论在抽象机器上运行的程序的终止,那么它会被真空满足,因为我们的程序没有终止。 如果它正在讨论编译器生成的实际程序的终止,那么C实现就会有问题,因为写入文件(stdout是一个文件)的数据不同于抽象机写入的数据。 (这个读数是由Hans Boehm引起的;我没有拿出这个标准中的微妙之处。)

    人们也可以做出一个较弱的观点,即需要在C11中创建一个允许清除空循环的暗示,这意味着这不是先前允许的优化。

    这是否也适用于无限循环?

    我相信它的意图是,这也适用于无限的goto循环。 在C ++中,这很明显,因为1.10节[intro.multithread]说:

    实现可能会假设任何线程最终都会执行以下操作之一

  • 终止,
  • 拨打图书馆的I / O功能,
  • 访问或修改易失性对象,或
  • 执行同步操作或原子操作。
  • 然后在N1528表达的N1528是C和C ++标准是一致的:

    由于编译器后端通常在C和C ++编译器之间共享,因此最重要的是WG14和WG21对采用的任何解决方案都达成一致。 替代方案将由后端对两种语言进行特殊处理,或者在处理C代码时禁用优化。 两者都不可取。

    最后说:

    WG21正在考虑改进措辞,使治疗保持一致。 希望WG14将追踪任何由此产生的变化。

    目前C11标准没有包含5.1.2.4多线程执行和数据N1528类似的措辞,但考虑到N1528 ,假设编译器会将无限goto循环视为C和C ++中未定义的行为似乎是明智的。

    另请注意,此处请参阅美国评论38和N3196,这是此更改所适用的论文。


    无法普遍检测到无限循环:请参阅暂停问题。 所以最好的编译器可以做一个很好的猜测 - 例如OP中提到的明显情况。

    但为什么这是可取的? 我可以看到发出警告并仍然允许行为,但是删除循环并不是“优化” - 它会改变程序的行为!


    循环不是死代码,它基本上阻止程序达到它后面的任何内容。 这不是如果循环被删除会发生什么,所以编译器不能删除循环。

    它可能会用一个依赖于平台的空闲指令来取代它,以通知处理器线程不会再做任何事情。

    编译器可以做的是删除循环后面的任何代码,因为它是无法访问的,永远不会执行。

    链接地址: http://www.djcxy.com/p/30711.html

    上一篇: Are compilers allowed to eliminate infinite loops?

    下一篇: Are compilers allowed to remove infinite loops like Intel C++ Compiler with