あるレベルの最適化を使用してコンパイルすると、C++コードが機能しないことがあります。コードを壊すのは、コンパイラが最適化を行っている場合と、コンパイラが意図したとおりに動作できるようにする未定義の動作を含むコードの場合があります。
より高い最適化レベルでのみコンパイルすると壊れるコードがあるとします。それがコードであるかコンパイラーであるかをどのようにして知ることができ、それがコンパイラーである場合はどうすればよいですか?
ほとんどの場合、コンパイラが壊れているのはあなたのコードであり、壊れているのは安全な賭けです。そして、それがコンパイラーであるという異常な場合でさえ、おそらく、特定のコンパイラーが準備されていない、いくつかのあいまいな言語機能を異常な方法で使用しています。つまり、コードをより慣用的なものに変更し、コンパイラーの弱点を回避することができます。
とにかく、(言語仕様に基づいて)コンパイラのバグを発見したことが証明できる場合は、コンパイラの開発者に報告してください。
他のバグと同様に、通常どおり、制御された実験を実行します。疑わしい領域を絞り込み、他のすべての最適化をオフにして、そのコードのチャンクに適用される最適化の変更を開始します。 100%の再現性が得られたら、コードの変更を開始し、特定の最適化を損なう可能性のあるものを導入します(たとえば、可能性のあるポインターエイリアシングの導入、潜在的な副作用を伴う外部呼び出しの挿入など)。デバッガーでアセンブリコードを確認することも役立ちます。
結果のアセンブリコードを調べて、ソースが要求することを実行するかどうかを確認します。オッズは非常に高く、いくつかの非自明な方法で実際にコードに問題があることを覚えておいてください。
30年以上のプログラミングで、私が見つけた正規のコンパイラ(コード生成)バグの数はまだわずか10です。同じ期間に見つけて修正した自分(および他の人)のバグの数はおそらく> 10,000。私の「経験則」は、コンパイラに起因する特定のバグの確率が0.001未満であることです。
私はコメントを書き始め、それが長すぎて要点が多すぎると判断しました。
私はあなたのコードが壊れていると主張します。万が一、コンパイラのバグを発見した場合は、コンパイラの開発者に報告する必要がありますが、違いはここで終わりです。
解決策は、問題のある構造を識別し、同じロジックを異なる方法で実行するようにリファクタリングすることです。バグがあなたの側にあるのか、コンパイラにあるのかにかかわらず、それで問題が解決する可能性が最も高くなります。
それがコードなのかコンパイラなのかを知りたい場合は、C++の仕様を完全に知っている必要があります。
疑問が解決しない場合は、x86アセンブリを完全に理解している必要があります。
両方を完全に学習する気分ではない場合、コンパイラーが最適化レベルに応じて異なる方法で解決するのは、ほぼ確実に未定義の動作です。
標準コードでコンパイルエラーまたは内部コンパイルエラーが発生することは、オプティマイザが間違っていることよりも可能性が高いです。しかし、コンパイラーがループを最適化して、メソッドが引き起こすいくつかの副作用を誤って忘れていると聞いたことがあります。
それがあなたかコンパイラかを知る方法についての提案はありません。別のコンパイラを試すこともできます。
ある日、それが私のコードかどうか疑問に思っていました。誰かがvalgrindを私に提案しました。私はそれを使ってプログラムを実行するために5または10分間費やしました(_valgrind --leak-check=yes myprog arg1 arg2
_はそれを実行したと思いますが、他のオプションで遊んだ)。それはすぐに、問題である特定のケースで実行される1行を示しました。その後、奇妙なクラッシュ、エラー、奇妙な動作がなく、私のアプリはスムーズに実行されました。 valgrindまたはそれと同様の別のツールは、そのコードかどうかを知るための良い方法です。
補足:アプリのパフォーマンスが低下したのはなぜか疑問に思いました。私のパフォーマンスの問題もすべて1行であることがわかりました。 for(int i=0; i<strlen(sz); ++i) {
と書きました。 szは数MBでした。何らかの理由で、コンパイラは最適化の後でも毎回strlenを実行しました。 1行は大きな問題になる可能性があります。パフォーマンスからクラッシュまで
ますます一般的な状況は、コンパイラーが標準で規定されていない動作をサポートするCの方言用に記述されたコードを破壊し、それらの方言を対象とするコードが厳密に準拠するコードよりも効率的であることを許可することです。このような場合、ターゲットの方言を実装したコンパイラで100%信頼できる「壊れた」コードとして説明することや、必要なセマンティクスをサポートしない方言を処理するコンパイラを「壊れた」と説明することは不公平です。 。代わりに、問題は、最適化が有効になっている最新のコンパイラーによって処理される言語が、かつて一般的であった(そして、最適化が無効になっている多くのコンパイラーによって、または最適化が有効になっているコンパイラーによっても処理される)から分岐しているという事実から単純に生じます。
たとえば、多くのコードは、gccの標準の解釈によって義務付けられていない多くのポインターエイリアスパターンを正当なものとして認識する方言用に書かれており、そのようなパターンを利用して、コードの直接的な変換をより読みやすく効率的にします。 C標準のgccの解釈の下で可能であるよりも。そのようなコードはgccと互換性がない可能性がありますが、それが壊れていることを意味するものではありません。それは、gccが最適化を無効にしてのみサポートする拡張機能に単に依存しています。
ほとんどの場合、コードにはいくつかの 未定義の動作 があります(他の人が説明しているように、C++コンパイラーが複雑すぎてバグがある場合でも、コンパイラーよりもコードにバグがある可能性が高くなります。 C++仕様には設計上のバグがあります)。そして、UBは、コンパイルされた実行可能ファイルが(不運にも)動作しても、ここに存在する可能性があります。
したがって、Lattnerのブログを読む必要があります すべてのCプログラマが未定義の動作について知っておくべきこと (そのほとんどはC++ 11にも適用されます)。
valgrind ツール、および最近の-fsanitize=
計測オプション から [〜#〜] gcc [〜#〜] (または Clang/LLVM )も参考になります。そしてもちろん、すべての警告を有効にします:g++ -Wall -Wextra
問題のある箇所を特定し、観察された動作を言語仕様に従って発生するはずの動作と比較します。確かに簡単ではありませんが、それはknow(そしてassumeだけではありません)を実行する必要があります。
そんなに細かいことはしないだろう。むしろ、私はコンパイラーメーカーのサポートフォーラム/メーリングリストに尋ねます。それが本当にコンパイラのバグである場合、彼らはそれを修正するかもしれません。おそらくそれはとにかく私のコードでしょう。たとえば、スレッド化でのメモリの可視性に関する言語仕様は非常に直観に反する場合があり、特定のハードウェア(!)で特定の最適化フラグを使用した場合にのみ明らかになる可能性があります。一部の動作は仕様で定義されていない可能性があるため、一部のコンパイラ/一部のフラグでは動作し、他の一部では動作しませんなど。