GCC:プログラムはコンパイルオプション-O3で動作しません
-
07-07-2019 - |
質問
最適化(オプション-O1、-O2、-O3など)でコンパイルすると動作しない(セグメンテーション違反が発生する)C ++プログラムを書いていますが、正常に動作します最適化せずにコンパイルします。
エラーがコード内にある可能性はありますか?または、これはGCCのバグであると想定すべきですか?
私のGCCバージョンは3.4.6です。
この種の問題に対する既知の回避策はありますか?
最適化されたバージョンと最適化されていないバージョンのプログラムには速度に大きな違いがあるため、最適化を使用する必要があります。
これは私の最初のファンクターです。最適化のレベルなしで正常に機能し、任意のレベルの最適化でセグメンテーションフォールトをスローするもの:
struct distanceToPointSort{
indexedDocument* point ;
distanceToPointSort(indexedDocument* p): point(p) {}
bool operator() (indexedDocument* p1,indexedDocument* p2){
return distance(point,p1) < distance(point,p2) ;
}
} ;
そして、これはあらゆるレベルの最適化で問題なく動作します:
struct distanceToPointSort{
indexedDocument* point ;
distanceToPointSort(indexedDocument* p): point(p) {}
bool operator() (indexedDocument* p1,indexedDocument* p2){
float d1=distance(point,p1) ;
float d2=distance(point,p2) ;
std::cout << "" ; //without this line, I get a segmentation fault anyways
return d1 < d2 ;
}
} ;
残念ながら、この問題は特定の値で発生するため再現が困難です。 1000を超えるベクトルから1つだけをソートすると、セグメンテーションエラーが発生するため、各ベクトルが持つ特定の値の組み合わせに依存します。
解決
コードフラグメントを投稿し、回避策(@Windowsプログラマーの回答)が見つかったので、おそらくあなたが探しているのは-ffloat-store
です。
-ffloat-store
レジスタに浮動小数点変数を格納しないでください。また、浮動小数点値をレジスタから取得するかメモリから取得するかを変更する可能性のある他のオプションを禁止します。
このオプションは、(68881の)浮動レジスタがdoubleが持つはずの精度よりも高い精度を維持する68000などのマシンで望ましくない過剰な精度を防ぎます。 x86アーキテクチャでも同様です。ほとんどのプログラムでは、余分な精度は良いだけですが、いくつかのプログラムはIEEE浮動小数点の正確な定義に依存しています。すべての関連する中間計算を変数に保存するように変更した後、そのようなプログラムに-ffloat-storeを使用します。
出典: http://gcc.gnu。 org / onlinedocs / gcc-3.4.6 / gcc / Optimize-Options.html
他のヒント
最初にあなたのコードが間違っていると思います。
わかりにくいですが。
コードは0警告でコンパイルされますか?
g++ -Wall -Wextra -pedantic -ansi
-O3を押すまでは、 動作するように見えるコードがあります。
#include <stdio.h>
int main()
{
int i = 0, j = 1, k = 2;
printf("%d %d %d\n", *(&j-1), *(&j), *(&j+1));
return 0;
}
最適化なしで、<!> quot; 2 1 0 <!> quot ;;最適化すると、<!> quot; 40 1 2293680 <!> quot;が得られます。どうして? iとkが最適化されたからです!
しかし、私はjのアドレスを取得し、jに割り当てられたメモリ領域から出ていました。これは標準では許可されていません。ほとんどの場合、問題は標準からの同様の逸脱が原因です。
valgrind は、このような場合に役立つことがよくあります。
編集:一部のコメント作成者は、標準では任意のポインター演算が許可されているという印象を受けています。 そうではありません。いくつかのアーキテクチャには面白いアドレッシングスキームがあり、アライメントが重要であり、特定のレジスタをオーバーフローさせると問題が発生する可能性があることに注意してください!
[ドラフト]規格の単語、ポインタへの整数の追加/ポインタからの減算(強調の追加):
<!> quot;ポインタオペランドと結果の両方が同じ配列オブジェクトの要素、または配列オブジェクトの最後の要素の1つを指す場合、評価はオーバーフローを生成しません;それ以外の場合、動作は未定義です。 <!> quot;
<!> amp; jは配列オブジェクトを指してさえいないため、<!> amp; j-1と<!> amp; j + 1は同じ配列オブジェクトの一部を指すことはほとんどありません。したがって、単に<!> amp; j + 1を評価するだけで(それを間接参照することはできません)、未定義の動作です。
x86では、ポインターに1を追加することはかなり安全であり、次のメモリー位置に移動するだけであると確信できます。上記のコードでは、そのメモリに何が含まれているかを推測すると問題が発生しますが、もちろん標準には近づいていません。
実験として、これによりコンパイラがすべてを一貫して強制的に丸めるかどうかを確認してください。
volatile float d1=distance(point,p1) ;
volatile float d2=distance(point,p2) ;
return d1 < d2 ;
エラーはコードにあります。おそらく最適化なしで動作するC標準に従って未定義の動作を呼び出す何かをしている可能性がありますが、GCCが最適化を実行するために特定の仮定を行うと、それらの仮定が正しくない場合にコードが壊れます。必ず-Wall
オプションを使用してコンパイルしてください。-Wextra
も良い考えかもしれません。警告が表示されるかどうかを確認してください。 -ansi
または-pedantic
を試すこともできますが、これらは誤検知を引き起こす可能性があります。
エイリアスの問題が発生している可能性があります(または、他の何百万もの可能性があります)。 -fstrict-aliasingオプションを調べます。
この種の質問は、追加情報なしでは適切に答えることができません。
コンパイラーの障害はめったにありませんが、コンパイラーにはバグがあり、さまざまな最適化レベルで現れることがあります(たとえば、最適化パスにバグがある場合)。
一般にプログラミングの問題を報告する場合:問題を示すために最小限のコードサンプルを提供します。コードをファイルに保存し、コンパイルして実行するだけです。問題を再現するには、できるだけ簡単にします。
また、異なるバージョンのGCCを試してください(特にLinuxでは、独自のGCCのコンパイルは非常に簡単です)。可能であれば、別のコンパイラで試してください。 Intel Cには、GCCとほぼ互換性のあるコンパイラがあります(そして、非営利的な使用には無料です)。これは問題の特定に役立ちます。
ほとんど( ほぼ )コンパイラではありません。
まず、-Wallを使用して、警告なしでコンパイルしていることを確認します。
それで<!> quot; eureka <!> quot;が得られなかった場合瞬間的に、クラッシュする最も最適化されていないバージョンの実行可能ファイルにデバッガーを接続し、実行内容と実行場所を確認します。
5は、この時点で問題を修正した10を取得します。
数日前に同じ問題に遭遇しましたが、私の場合はエイリアシングでした。また、GCCは他のコンパイラと比較して、異なる方法で動作しますが、間違っては動作しません。 GCCは、C ++標準のルール弁護士と呼ばれるものになりました。その実装は正しいですが、C ++で本当に正しくなければならないか、何かを過剰に最適化することになります。しかし、スピードは得られるので、文句を言うことはできません。
いくつかのコメントを読んだ後、ここでいくつかの投票権を取得する予定ですが、コンソールゲームプログラミングの世界では、より高い最適化レベルが奇妙なエッジのケースで誤ったコードを生成することがあるというのはかなり一般的な知識です。ただし、コードにわずかな変更を加えることでエッジケースを修正できる可能性が非常に高いかもしれません。
わかりました...
これは私が今まで経験した中で最も奇妙な問題の1つです。
私はそれがGCCのバグであると述べるのに十分な証拠があるとは思わないが、正直なところ...それは本当に1つに見える。
これは私の最初のファンクターです。最適化のレベルなしで正常に機能し、任意のレベルの最適化でセグメンテーションフォールトをスローするもの:
struct distanceToPointSort{
indexedDocument* point ;
distanceToPointSort(indexedDocument* p): point(p) {}
bool operator() (indexedDocument* p1,indexedDocument* p2){
return distance(point,p1) < distance(point,p2) ;
}
} ;
そして、これはあらゆるレベルの最適化で問題なく動作します:
struct distanceToPointSort{
indexedDocument* point ;
distanceToPointSort(indexedDocument* p): point(p) {}
bool operator() (indexedDocument* p1,indexedDocument* p2){
float d1=distance(point,p1) ;
float d2=distance(point,p2) ;
std::cout << "" ; //without this line, I get a segmentation fault anyways
return d1 < d2 ;
}
} ;
残念ながら、この問題は特定の値で発生するため再現が困難です。 1000を超えるベクトルから1つだけをソートすると、セグメンテーションエラーが発生するため、各ベクトルが持つ特定の値の組み合わせに依存します。
うわー、私はそんなに素直に答えを期待していなかった、そして非常に多くの...
std :: sort()を使用してstd :: vectorのポインターをソートするとエラーが発生します
厳密な弱順序ファンクターを提供します。
しかし、私が提供したファンクターは正しいものであることがわかっています。
さらに、ベクトルを並べ替えるときにエラーが発生するため、エラーはベクトル内の無効なポインターにはなりません。最初にstd :: sortを適用せずにベクトルを反復処理すると、プログラムは正常に動作します。
GDBを使用して、何が起こっているのかを見つけようとしました。このエラーは、std :: sortがファンクターを呼び出すときに発生します。明らかにstd :: sortは、無効なポインターをファンクターに渡しています。 (もちろん、これは最適化されたバージョンでのみ発生し、あらゆるレベルの最適化-O、-O2、-O3)
他の人が指摘したように、おそらく厳密なエイリアスです。 o3でオフにして、再試行してください。私の推測では、ファンクターでポインタートリックを実行している(int比較としての高速フロート?下位2ビットのオブジェクトタイプ?)ことは、テンプレート関数のインライン化で失敗します。 警告は、このケースをキャッチするのに役立ちません。 <!> quot;コンパイラがすべての厳密なエイリアシングの問題を検出できた場合、回避することもできます<!> quot;無関係なコード行を変更するだけで、レジスタの割り当てが変更されると、問題が発生したり消えたりする可能性があります。
更新された質問に;)が表示されるように、std::vector<T*>
に問題が存在します。ベクトルに関する一般的なエラーの1つは、resize()dであるべきものをreserve()することです。その結果、配列の境界外に書くことになります。オプティマイザーはこれらの書き込みを破棄する場合があります。
距離を置いてコードを投稿してください!おそらくいくつかのポインターマジックを実行します。以前の投稿を参照してください。中間割り当てを行うと、レジスタの割り当てを変更することでコードのバグが隠されるだけです。これをさらに伝えるのは、出力が変化することです!
真の答えは、このスレッドのすべてのコメントの中に隠されています。まず、コンパイラのバグではありません。
問題は浮動小数点の精度に関係しています。 distanceToPointSort
は、引数(a、b)と(b、a)の両方に対してtrueを決して返さない関数でなければなりませんが、一部のデータパスに高い精度を使用することをコンパイラが決定すると、まさにそのようになります。この問題は特に発生する可能性がありますが、-mfpmath=sse
なしのx86に限定されるものではありません。コンパレーターがそのように動作する場合、sort
関数は混乱する可能性があり、セグメンテーション違反は驚くべきことではありません。
ここでは-ffloat-store
が最良の解決策であると考えています(すでにCesarBが提案しています)。