【问题标题】:Are compilers allowed to remove infinite loops like Intel C++ Compiler with -O2 does?编译器是否允许像带有 -O2 的英特尔 C++ 编译器那样删除无限循环?
【发布时间】:2011-04-01 11:12:16
【问题描述】:

以下测试代码在 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())。但是,当在 ICC(发布)下启用 -O2 时,它开始无限地“做某事”。

既然这是单线程程序并且 isEmpty() 应该被评估为true,我找不到 ICC 应该以这种方式运行的理由?我错过了什么吗?

【问题讨论】:

  • 如果 m_first 和 m_last 被声明为 'volatile' 有帮助吗?我无权访问 ICC。
  • 另一个疯狂的想法:它是否与 printf 未指定 %s 的事实有关。 printf("%s", "不应该到达这里\r\n");?
  • 指出您正在测试哪个版本的 ICC 以及在哪个平台上对您很有帮助。
  • @Samuel:如果让成员“易变”有帮助,我想知道供我参考。这应该抑制任何优化标志效果

标签: c++ optimization icc


【解决方案1】:

因为while (q.isEmpty()) ; 循环不包含任何可能导致外部可见副作用的语句,所以整个循环正在被优化而不再存在。同理:

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

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

在 C 语言中,是否允许以这种方式优化无限循环实际上是一个突出的争论点(我不知道 C++ 的情况如何)。据我所知,在这个问题上从未达成共识——聪明和有见识的人已经采取了双方。

【讨论】:

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

这听起来确实像一个错误。这是一个(非常疯狂的)猜测,可能是什么推理导致了它......

内联后,它看到:

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

并且do nothing repeatedly ; do something 的任何序列都可以简单地翻译为“做某事”。如果重复的部分是无止境的(如本例所示),它会下降。但也许他们不会在故意进行无限循环的示例上测试他们的编译 ;-)。

【讨论】:

  • 检测有意的无限循环基本上是停止问题,因此他们假设,由于它没有可见的副作用,它什么也不做,因此对程序无用,可能会被删除。显然,它是否完成对我们来说是一个可见的 sde-effect,但对于标准定义的 C/C++ 抽象机来说却不是。如果你想要循环,那么你必须告诉编译器它确实会影响程序,例如,使读取变量之一变为 volatile
【解决方案3】:

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

【讨论】:

  • 1.有分号 2。与 while(q.isEmpty()) {} 或 while(q.isEmpty()) {true;} 的结果相同
  • 致任何人:否决票真的有必要吗?我在 SO 上看到了许多案例,其中发布的代码不是正在使用的确切代码,并且实际问题在翻译中被遗漏或修复。我只是想帮助确保这不是一个简单但容易忽略的错误的结果。 (毕竟,我们不是偶尔会因为分号错误而在错误上浪费时间吗?)
【解决方案4】:

顺便说一句,这个版本的 icc 可以满足您的需求。也就是说,它从不调用doSomething()

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

【讨论】:

    【解决方案5】:

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

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

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

    【讨论】:

      【解决方案6】:

      最好的办法是将生成的二进制步骤放入其中并反汇编 main 函数并查看生成的程序集。并不是说您将能够看到错误,但您可以查看是否优化了某些内容。

      【讨论】:

      • 你不能。 asm 代码优化了所有内容,您什么也看不到。
      • 我不确定这意味着什么。你是说你反汇编了生成的二进制文件并且 while() 循环消失了?
      【解决方案7】:

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

      【讨论】:

        猜你喜欢
        • 2013-11-11
        • 1970-01-01
        • 2017-10-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-08
        相关资源
        最近更新 更多