あるレベルの最適化でコンパイルすると、C ++コードが機能しない場合があります。それは、コードを壊す最適化を行っているコンパイラかもしれませんし、コンパイラが感じることなら何でもできるようにする未定義の振る舞いを含むコードかもしれません。
より高い最適化レベルでのみコンパイルされたときに壊れるコードの一部があるとします。コードまたはコンパイラーであるかどうか、またコンパイラーである場合はどうすればよいですか?
あるレベルの最適化でコンパイルすると、C ++コードが機能しない場合があります。それは、コードを壊す最適化を行っているコンパイラかもしれませんし、コンパイラが感じることなら何でもできるようにする未定義の振る舞いを含むコードかもしれません。
より高い最適化レベルでのみコンパイルされたときに壊れるコードの一部があるとします。コードまたはコンパイラーであるかどうか、またコンパイラーである場合はどうすればよいですか?
回答:
大半の場合、壊れているのはコンパイラではなく、あなたのコードであるというのは安全な賭けだと思います。そして、それがコンパイラーであるという異常なケースでさえ、おそらく特定のコンパイラーが準備されていない、あいまいな方法でいくつかのあいまいな言語機能を使用しているでしょう。つまり、コードをより慣用的なものに変更し、コンパイラの弱点を回避できる可能性が高いと言えます。
とにかく、(言語仕様に基づいて)コンパイラのバグを発見したことを証明できる場合は、コンパイラの開発者に報告してください。
いつものように、他のバグと同様に、制御された実験を実行します。疑わしい領域を絞り込み、他のすべての最適化をオフにして、そのコードチャンクに適用される最適化の変更を開始します。100%の再現性が得られたら、コードの変更を開始し、特定の最適化を妨げる可能性のあるものを導入します(たとえば、ポインターエイリアシングの可能性の導入、潜在的な副作用を伴う外部呼び出しの挿入など)。デバッガーでアセンブリコードを確認することも役立ちます。
プログラミングの30年以上で、私が見つけた本物のコンパイラ(コード生成)バグの数はまだ10にすぎません。 > 10,000。私の「経験則」は、特定のバグがコンパイラーに起因する確率は<0.001であるということです。
int + int
ハードウェアのADD命令にコンパイルされるかのように、オーバーフローに依存するコードを書きました。古いバージョンのGCCでコンパイルした場合は正常に機能しましたが、新しいコンパイラーでコンパイルした場合は正常に機能しませんでした。どうやらGCCの優秀な人々は、整数オーバーフローの結果が未定義であるため、オプティマイザーは決して起こらないという仮定の下で動作する可能性があると判断しました。重要なブランチをコードから最適化しました。
コードかコンパイラかを知りたい場合は、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(またはクラン/ LLVMは)、また助けになるはずです。そしてもちろん、すべての警告を有効にします:g++ -Wall -Wextra