为什么现代C++编译器不能优化这样的简单循环?(Clang,MSVC)

Meh*_*dad 24 c++ gcc clang compiler-optimization visual-c++

当我使用Clang(-O3)或MSVC(/O2)编译并运行此代码时...

#include <stdio.h>
#include <time.h>

static int const N = 0x8000;

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i)
    {
        int a[N];    // Never used outside of this block, but not optimized away
        for (int j = 0; j < N; ++j)
        {
            ++a[j];  // This is undefined behavior (due to possible
                     // signed integer overflow), but Clang doesn't see it
        }
    }
    clock_t const finish = clock();
    fprintf(stderr, "%u ms\n",
        static_cast<unsigned int>((finish - start) * 1000 / CLOCKS_PER_SEC));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

......循环没有得到优化.

此外,既不锵3.6 也不 VISUAL C++ 2013 ,也不 GCC 4.8.1告诉我,该变量未初始化!

现在我意识到缺乏优化并不是一个错误本身,但我发现这令人惊讶,因为编译器现在应该是非常聪明的.这看起来就像是一段简单的代码,即使是十年前的活跃分析技术也应该能够优化掉变量a,从而完成整个循环 - 更不用说递增变量已经是未定义的行为.

然而,只有海湾合作委员会能够发现它是一个无操作,并且没有一个编译器告诉我这是一个未初始化的变量.

为什么是这样?什么阻止简单的活性分析告诉编译器a未使用?而且,为什么编译器首先检测到a[j]未初始化?为什么所有这些编译器中现有的未初始化变量检测器都无法捕获这个明显的错误?

Ric*_*ith 6

未定义的行为在这里无关紧要.用以下内容替换内循环:

    for (int j = 1; j < N; ++j)
    {
        a[j-1] = a[j];
        a[j] = j;
    }
Run Code Online (Sandbox Code Playgroud)

...具有相同的效果,至少对于Clang来说.

问题是,内环从两个负载a[j](一些j)和商店a[j](一些j).没有任何商店可以删除,因为编译器认为它们可能对以后的加载可见,并且没有任何加载可以被删除,因为它们的值被使用(作为后期商店的输入).因此,循环仍然会对内存产生副作用,因此编译器看不到它可以被删除.

与nm的答案相反,替换intunsigned不会使问题消失.Clang 3.4.1 使用int使用unsigned int生成的代码是相同的.

  • 在此示例中用`unsigned`s替换所有`int`s'不会改变行为.比较生成的代码[之前](http://goo.gl/kL9jd4)和[之后](http://goo.gl/A306w4).nm的答案是错误的. (3认同)
  • @jthill 好点。然而,初始化数组在这里也没有任何区别;代码仍然没有被删除。 (2认同)

nh_*_*nh_ 2

这确实很有趣。我用 MSVC 2013 尝试了你的例子。我的第一个想法是 ++a[j] 有点未定义这一事实是循环未被删除的原因,因为删除它肯定会改变程序从未定义的含义/对有意义的东西的语义不正确,所以我尝试初始化之前的值,但循环仍然没有消失。

后来我替换了++a[j]; a[j] = 0;然后产生一个没有任何循环的输出,因此两次调用 Clock() 之间的所有内容都被删除了。我只能猜测原因。也许优化器无论出于何种原因都无法证明operator++没有副作用。