问题 编译器是否允许删除无限循环,例如带有-O2的英特尔C ++编译器?


以下测试代码在VS中正确地执行调试或发布,以及在GCC中。它也适用于带调试的ICC,但在启用优化时却没有(-O2)。

#include <cstdio>

class tClassA{
public:
  int m_first, m_last;

  tClassA() : m_first(0), m_last(0) {}
  ~tClassA() {}

  bool isEmpty() const {return (m_first == m_last);}
  void updateFirst() {m_first = m_first + 1;}
  void updateLast() {m_last = m_last + 1;}
  void doSomething() {printf("should not reach here\r\n");}
};

int main() {
  tClassA q;
  while(true) {
    while(q.isEmpty()) ;
    q.doSomething();
  }
  return 1;
}

它应该停在 while(q.isEmpty())。什么时候 -O2 然而,在ICC(发布)下启用它,无限启动“doSomething”。

由于这是单线程程序   isEmpty() 应评估为 true,我觉得ICC没有理由这样做吗?我想念什么吗?


5826
2017-08-20 02:45


起源

如果m_first和m_last被声明为'volatile'会有帮助吗?我无法访问ICC。 - Chubsdad
另一个疯狂的想法:它与%s未指定printf这一事实有关。 printf(“%s”,“不应该到达这里\ r \ n”);? - Chubsdad
有关: 编译器是否允许消除无限循环? - James McNellis
您可以指出您正在测试的ICC版本以及在哪个平台上。 - James McNellis
@Samuel:想知道我的参考是否让成员'挥发'得到了帮助。这应该抑制任何优化标志效果 - Chubsdad


答案:


因为 while (q.isEmpty()) ; 循环不包含任何可能导致外部可见副作用的语句,整个循环优化不存在。这与以下原因相同:

for (int i = 0; i < 10; i++)
    ;

可以优化不存在,只要 i 不是 volatile (商店到 volatile 对象是程序“外部可见”效果的一部分。

在C语言中,它实际上是一个突出的争论焦点,是否允许以这种方式优化无限循环(我不知道C ++的情况是什么)。据我所知,在这个问题上从来没有达成共识 - 聪明而知识渊博的人已经采取了双方的立场。


10
2017-08-20 03:16



这可能是原因。以下代码正常工作:static int A = 0; while(q.isEmpty()){A = A + 1;}但不是以下内容:static int A = 1; while(q.isEmpty()){A;} - Samuel
有趣。无限循环无所事事 - 很好的优化机会(并且可以理解为什么有人可能不同意)。 - Suma
还有一件事:如果while(true)用于while(q.isEmpty()),则代码可以正常工作。 - Samuel
Suma:就标准而言,它是正确的行为,因为它没有任何可见的副作用,它不可能改变程序的行为,对吧? (错误 - 一个无限循环会改变程序,但是由于检测循环是否无限,在一般情况下,基本上是停止问题,它决定总是删除这样的循环。如果这是不合需要的,那么它程序员可以告诉编译器它实际上有副作用,例如,通过读取或写入易失性varibale或某些特定于编译器的标志(如果存在) - Dan
@Samuel:请注意,在你的第一个例子中, A 最终溢出,产生未定义的行为。由于优化器可能认为UB没有发生,因此可以推断出 q.isEmpty() 在开始时必须是假的,并且可以消除整个循环。 - MSalters


这听起来像个臭虫。这是一个(非常疯狂的)猜测可能导致它的原因......

内联后,它看到:

while (q.m_first == q.m_last) /* do nothing */ ;
do_something();

和任何序列 do nothing repeatedly ; do something 可以简单地翻译成“做某事”。如果重复的部分是无穷无尽的(如在这种情况下),则这会下降。但也许他们没有测试他们编译故意无限循环的例子;-)。


2
2017-08-20 02:55



检测有意无限循环基本上是暂停问题,因此他们认为,由于它没有可见的副作用,它什么都不做,因此对程序没用,可能会被删除。显然,它是否完成对我们来说是一个可见的sde效应,而不是标准定义的C / C ++抽象机器。如果你想要循环,那么你必须告诉编译器它确实会影响程序,例如,使其中一个读取变量不稳定 - Dan


您构建和运行的实际代码有可能在之后丢失分号 while(q.isEmpty()) ?这肯定会导致下一行被无限调用。


1
2017-08-20 03:05



+1提醒我类似的东西 - Chubsdad
1.有分号2.与while(q.isEmpty()){}或while(q.isEmpty()){true;}的结果相同 - Samuel
对谁:真的有必要进行投票吗?我在SO上看过很多案例,其中发布的代码不是所使用的确切代码,并且在翻译中遗漏或修复了实际问题。我只是想帮助确保这不是一个简单但容易被忽视的错误的结果。 (毕竟,难道我们都不会因为错误的分号之类的东西而偶尔浪费时间吗?) - TheUndeadFish


稍微说一下,这个版本的icc可以满足您的需求。也就是说,它从不打电话 doSomething()

[9:41am][wlynch@computer /tmp] icc --version
icc (ICC) 11.0 20081105

1
2017-08-20 13:42





C ++标准允许删除没有副作用的循环,即使它们没有终止:

人们普遍认为它是   允许转型很重要   潜在的非终止循环   (例如,通过合并两个循环   迭代相同的潜在   无限集,或通过消除a   无副作用的循环),即使是那样   可能没有其他理由   从来没有第一个循环的情况   终止。    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2429.htm

请参阅此处的讨论: http://blog.regehr.org/archives/161


1
2017-08-21 06:12





最好的办法是将生成的二进制步骤放入其中并拆分主函数并查看生成的汇编。不是说你将能够看到一个错误,但你可以看到是否有一些东西被优化了。


0
2017-08-20 02:53



你不能。 asm代码优化了所有内容,你什么都看不到。 - Samuel
我不确定这意味着什么。你是说你拆解了生成的二进制文件而while()循环消失了吗? - linuxuser27


我想这可能是你的gcc版本。我在4.4.2下编译了你的编程,它完全按照应有的方式工作。


-3
2017-08-20 03:02



这是icc,而不是gcc - Merlyn Morgan-Graham