web-dev-qa-db-ja.com

Visual Studio 2015 Update 3-C ++コンパイラのバグ?

VS2015 Update3コンパイラで、明らかな理由もなくコードの一部が省略されるという奇妙なケースが見られます。

私たちはそれを発見しました

  • これはVS2015Update3で発生します(ヘルプ| Aboutは14.0.25431.01 Update 3、cl.exeバージョン19.00.24215.1と言います)
  • これはVS2015Update2で​​は発生しません(ヘルプ| Aboutは14.0.25123.00 Update 2、cl.exeバージョン19.00.23918と言っています)
  • これは、最適化がオンになっている場合にのみ発生します(たとえば、デフォルトのリリース構成で)
  • X86とx64の両方で発生します
  • コードスニペットが新しい「Win32コンソールアプリケーション」に挿入されたときに発生します(つまり、特別なコマンドラインオプションは必要ありません)

このスニペットの原因コードを最小限に抑えることができました。

#include <stdio.h>
#include <tchar.h>
#include <stdlib.h>

int _tmain(int, _TCHAR*[])
{
    volatile int someVar = 1;

    const int indexOffset = someVar ? 0 : 1;    // Loop omitted
    // const int indexOffset = !someVar;        // Loop omitted
    // const int indexOffset = 0;               // Good
    // const int indexOffset = 1;               // Good
    // const int indexOffset = someVar;         // Good
    // const int indexOffset = someVar + 1;     // Good

    for (int i = 1 - indexOffset; i < 2 - indexOffset; ++i)
    {
        printf("Test passed\n");
    }

    return 0;
}

「ループ省略」と書かれている行の場合、コンパイラによってループ本体全体が省略されます。どうして?私の知る限り、未定義の動作はありません。


最初の「ループ省略」の分解:

int _tmain(int, _TCHAR*[])
{
01151010  Push        ebp  
01151011  mov         ebp,esp  
01151013  Push        ecx  
    volatile int someVar = 1;
01151014  mov         dword ptr [ebp-4],1  

    const int indexOffset = someVar ? 0 : 1;    // Loop omitted
0115101B  mov         eax,dword ptr [someVar]  
    // const int indexOffset = !someVar;        // Loop omitted
    // const int indexOffset = 0;               // Good
    // const int indexOffset = 1;               // Good
    // const int indexOffset = someVar;         // Good
    // const int indexOffset = someVar + 1;     // Good

    for (int i = 1 - indexOffset; i < 2 - indexOffset; ++i)
    {
        printf("Test passed\n");
    }

    system("pause");
0115101E  Push        offset string "pause" (011520F8h)  
01151023  call        dword ptr [__imp__system (0115205Ch)]  
01151029  add         esp,4  
    return 0;
0115102C  xor         eax,eax  
}
0115102E  mov         esp,ebp  
01151030  pop         ebp  
01151031  ret

テストプロジェクト: http://dropmefiles.com/S7mwT


オンラインでお試しください!

  • http://webcompiler.cloudapp.net/ に移動します
  • サンプルコードをエディターに配置します
  • プット/O2からAdditional compiler flags
  • 小切手 Run executable after compilation

バグレポート: https://developercommunity.visualstudio.com/content/problem/71906/compiler-optimization-code-generation-bug.html

35
Codeguard

はい、それはバグです。具体的には、VS2015 Update 3で導入された新しいSSAオプティマイザーのバグです。 文書化されていないコマンドラインオプション-d2SSAOptimizer-は、代わりに古いオプティマイザを使用するようにコンパイラバックエンドに指示します。これにより、バグは顕在化しません

参考までに、再現を最小限に抑えることができます。

int main()
{
    volatile int someVar = 1;

    const int indexOffset = someVar ? 0 : 1;

    for (int i = 1 - indexOffset; i < 2 - indexOffset; ++i)
    {
        return 0;
    }
    return 1;
}

これは、コンパイラ開発者が問題をより迅速に特定するのに役立ちます。


Codeguardからの追加(Caseyの答えはTHEであると判断しました回答):Microsoftから返信を受け取りました(ブログ投稿の作成者であるGratian Lup 新しい高度なVisual C++コードオプティマイザーの紹介 ):

はい、これは確かにSSAオプティマイザー自体のバグです。通常、新しいオプティマイザーにあると報告されているバグのほとんどは他の部分にあり、20年後に明らかになることもあります。

それは小さな選択です。オーバーフローがない場合、(a --Const1)CMP(a --Const2)のように見える比較を削除しようとします。問題は、コードに(1-indexOffset)CMP(2-indexOffset)があり、減算はもちろん可換ではないことです-しかし、オプティマイザーコードはそれを無視し、(1-indexOffset)を(indexOffset-1)のように処理します。

この問題の修正は、VS2017の次の大きなアップデートでリリースされます。それまでは、SSAオプティマイザーを無効にすることは適切な回避策です。この関数のみの最適化を無効にすることは、物事をそれほど遅くしないのであれば、より良いアプローチかもしれません。これは#pragmaoptimize( ""、off)で実行できます: https://msdn.Microsoft.com/en-us/library/chh3fb0k.aspx

24
Casey