Visual Studio C ++でエラーを検索して発見した方法

素晴らしい夏の日でした。 窓の外では、雲が輝いていて、カラスは優しい声で歌っていました。車は洗車でシャンプーで元気に染まりました。



変更をテスト部門に転送する前に変更をチェックするためにリリースモードでアセンブルしたプログラムを起動した瞬間まで、問題を正確に予測するものはありませんでした。



背景



私たちの会社は比較的長い間存在しており、主要製品はすでに会社の従業員の何人かよりも古いため、十分な古代のコードがあります。 それでも、最新の状態を維持しようとしています。ModernC ++が積極的に使用されているため、約1年前にメインプロジェクトがVC2015に移行されました。 馬、タンバリン、ブラックジャック、バレリアンがいる別のサーカスでした。 ヘルパーコードは、時間と欲求が現れると翻訳されます。 この場合、このような補助プロジェクトの1つをVC2015に移行することにしました。これは、テクニカルサポートで非常に積極的に使用されています。



そのような移行の落とし穴を既に知っていると確信しており、タスクは1時間以内で完了します。



外側は、プログラムは単純です。異なるデータベーステーブルから取得した行のリストを表示します。 ユーザーが行を選択すると、リストの列見出しが対応する表の列名に変わります。



そして、私はこれが起こっていないことに気付きます。



生体内



何度かチェックしますが、再現性は絶対です。 プロジェクトをゼロから収集し、実行し、確認します。 ゼロから質量。



これは奇妙でした。デバッグ中に列が正しく切り替わったことは絶対に覚えており、これはテストケースの一部でした。 自分の健全性を確認するために、デバッグバージョンを起動し、列が切り替えられていることを確認します。 また、最適化をオンにしたらすぐにエラーが表示されるようにします。



なんて素晴らしい日だ。



ヘッダー更新機能自体は非常に簡単です。 最初に、彼女は特定の条件を考慮し、次にこのコードのようなものが機能します。

int flag = !application->settings.showsize; // showsize   BYTE int first = columnData - flag; int last = ALL_COLUMNS - flag; if (condition) { for (int i = first; i < last; i++) { listctrl.SetColumn(i, "- "); } } else { for (int i = first; i < last; i++) { listctrl.SetColumn(i, "-  "); } }
      
      





ウランのスリッパを投げないようにお願いします、私はすでにコードがかなり古いと言いました。



したがって、どのような状況でも、少なくとも何かが起こるはずでした。 さらに、デバッガーはこのコードに到達すると、単に関数の最後にジャンプします。 逆アセンブラーを開くと、このコード(およびその前の10行)がすべてリストにないことがわかります。 もちろん、最適化されたコードは、スパゲッティやフェルトブーツコードよりもクールに見える場合があります。 しかし、この場合、関数の最初からどこにも、他の場所での遷移のヒントすらありません。 リストには、条件の計算と、その直後の出力が表示されます。



エラーが最初に見えたよりもはるかに興味深いことを理解し始めています。私たちの専門家に電話をかけています。社内の「未知のがらくた」などの状況に対処し、夕食を食べない人は、Windowsのバグを見つけてください。 「コンパイラにエラーはありません」というトピックに関する幻想はありませんが、経験から、「コンパイラのエラー」の99.99%がプログラム開発者の手にかかっていることが示唆されています。



生体外



最適化されたコードでのみエラーが発生した場合、これはどこかで不明なUBにつまずいたことを意味し、その行が最初の候補になる
 int flag = !application->settings.showsize;
      
      



問題は本当にここのどこかにあることがすぐに明らかになりますが、「すべてがそれほど単純ではない」。 コードが魔法のようにリストに表示されたように、式を定数、別の変数に置き換えて、否定の代わりに三項演算子を置くか、少なくともスタックに構造体を置く価値がありました。



最良のアイデアがないため、この関数を別のクリーンなプロジェクトに引き込み、不必要に不要なものをすべて捨てます。 次に、構造とポインターを使用したシャーマニズムを通常のvolatileに置き換えることができることがわかります。

 #include <stdio.h> int main() { volatile int someVar = 1; const int indexOffset = someVar ? 0 : 1; //   // const int indexOffset = !someVar; //   // const int indexOffset = 0; //   // const int indexOffset = 1; //   // const int indexOffset = someVar; //   // const int indexOffset = someVar + 1; //   for (int i = 1 - indexOffset; i < 2 - indexOffset; ++i) { printf("Test passed\n"); } return 1; }
      
      





元のコードでは、行の三項演算子で否定を置き換えるため、ここで非常に驚きました。
 int flag = !application->settings.showsize;
      
      



所定の場所にコードを返しましたが、volatileに対しては機能しませんでした。



コンパイラのエラーに出くわしたことはほぼ間違いありませんでしたが、数十メガバイトのコードに同様の部分がないことは信じられないように思えました。



調査



アンチウイルスが突然Update 3でコンパイルされたプログラムを宣誓し始めたため、メインプログラムがvs2015 Update 2でアセンブルされていることは注目に値します。 しかし、私を含む一部の開発者はUpdate 3をインストールしました。いくつかの異なるコンピューターとVSのバージョンを確認したところ、Update 3にのみエラーが存在することがわかりました。後で「Update 3で開始」と書く方が正しいことがわかりました。



グーグルは彼が廃業していることを明らかにしたので、次の論理的なステップは書くことでした

stackoverflow に関する質問 。 「コンパイラーで間違いを見つけました」というフレーズからStackOverflowに質問を送信することは、手を切ってサメと一緒にプールに飛び込むことと同じですが、この場合、サメはいっぱいで友好的でした。 ほんの数分で、テストケースはさらに簡素化され、さまざまなコンパイラによるこのコードの翻訳結果を確認できるツールによって促され、さらに重要なことには、「 VS2015 Update 3で導入された新しいSSAオプティマイザー 」という魔法のフレーズが鳴りました 。 そこには、新しいオプティマイザーを無効にするマジックキー-d2SSAOptimizer-も記載されていました。



今回、Googleはブログに私たちを連れて行きました。VisualC ++オプティマイザーチーム開発者による新しい高度なVisual C ++コードオプティマイザーの紹介と、使用したエラーメッセージを送信する提案。 そして、文字通り10〜15分後に彼らは次の答えを受け取りました:

はい、これは間違いなくSSAオプティマイザー自体の誤りです。通常、オプティマイザーのエラーとして報告されるエラーのほとんどは他の場所にあり、20年後にしか現れないことがあります。



エラーは、オーバーフローが発生しない場合、フォーム(a-Const1)CMP(a-Const2)の比較を削除しようとする小さな最適化にあります。 コードに式(1-indexOffset)CMP(2-indexOffset)が含まれているためエラーが発生します。もちろん、減算は可換ではありませんが、オプティマイザーはこれを考慮せず(1-indexOffset)、あたかも( indexOffset-1)。



このエラーの修正は、VS2017の次の大きな更新で公開されます。 このときまで、この機能のSSAオプティマイザーを無効にすることは、深刻な速度低下を引き起こさない場合に適したソリューションです。 これは、#pragma optimize( ""、off)を使用して実行できます。msdn.microsoft.com / en-us / library / chh3fb0k.aspx
オリジナル
はい、これは確かにSSAオプティマイザー自体のバグです。通常、新しいオプティマイザーに存在すると報告されているほとんどのバグは他の部分にあり、20年後には現在公開されています。



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



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


エピローグ



調査が示したように、現在、すべてのVC ++コンパイラーは、バージョン2015 Update 3から最新のバージョンで終わるこのエラーの影響を受けます。 パッチがいつリリースされるかはまだ不明であるため、プログラムからコードの一部が奇跡的に消えた場合、新しいオプティマイザーがこのコードを必要としていると判断したかどうかを確認してください。



修正プログラムがVS2017に対してのみリリースされるのは少し残念ですが、今ではそれをどう処理するかがわかっています。



なんて素晴らしい日だ!



このエラーを見つけてくれたCodeguardに感謝します。



All Articles