10

最適化 (オプション -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 ;
    }
} ;

残念ながら、この問題は特定の値で発生するため再現が困難です。1,000 以上のベクトルのうちの 1 つだけをソートするとセグメンテーション違反が発生するため、各ベクトルが持つ値の特定の組み合わせに大きく依存します。

4

16 に答える 16

8

私はあなたのコードが最初に間違っていると思います。
わかりにくいですが。

あなたのコードは0の警告でコンパイルされますか?

 g++ -Wall -Wextra -pedantic -ansi
于 2008-11-11T05:17:12.233 に答える
8

コードフラグメントを投稿し、有効な回避策が見つかったので (@Windows プログラマーの回答)、おそらくあなたが探しているのは-ffloat-store.

-float ストア

浮動小数点変数をレジスタに格納しないでください。また、浮動小数点値がレジスタまたはメモリから取得されるかどうかを変更する可能性のある他のオプションを禁止します。

このオプションは、(68881 の) 浮動レジスターが double の想定よりも高い精度を維持する 68000 などのマシンで、望ましくない過剰な精度を防ぎます。x86 アーキテクチャの場合も同様です。ほとんどのプログラムでは、過剰な精度は有効ですが、少数のプログラムは IEEE 浮動小数点の正確な定義に依存しています。そのようなプログラムには、関連するすべての中間計算を変数に格納するように変更した後、-float-store を使用します。

ソース: http://gcc.gnu.org/onlinedocs/gcc-3.4.6/gcc/Optimize-Options.html

于 2008-11-12T13:35:09.013 に答える
7

-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;
}

最適化を行わないと、「2 1 0」になります。最適化すると、「40 1 2293680」が得られます。なんで?i と k が最適化されたからです。

しかし、私はjのアドレスを取得して、jに割り当てられたメモリ領域から出ていました. これは、標準では許可されていません。あなたの問題は、標準からの同様の逸脱によって引き起こされる可能性が最も高い.

valgrindは、このようなときに役立つことがよくあります。

編集: 一部のコメンターは、標準では任意のポインター演算が許可されているという印象を受けています。そうではありません。いくつかのアーキテクチャーは変なアドレッシングスキームを持っていることを覚えておいてください。アラインメントが重要かもしれず、特定のレジスターをオーバーフローさせると問題が発生するかもしれません!

[ドラフト]標準の言葉、ポインターへの/からの整数の加算/減算に関する(強調を追加):

「ポインタ オペランドと結果の両方が同じ配列オブジェクトの要素を指している場合、または配列オブジェクトの最後の要素の 1 つ後ろを指している場合、評価はオーバーフローを生成しません。それ以外の場合、動作は未定義です。

&j は配列オブジェクトを指していないので、&j-1 と &j+1 は同じ配列オブジェクトの一部を指すことはほとんどありません。したがって、単純に &j+1 を評価する (逆参照することは言うまでもなく) ことは、未定義の動作です。

x86 では、ポインターに 1 を追加することはかなり安全であり、次のメモリ位置に移動するだけであると確信できます。上記のコードでは、そのメモリに何が含まれているかを仮定すると問題が発生しますが、これはもちろん標準には近づきません。

于 2008-11-11T07:14:53.027 に答える
5

エラーはコードにあります。最適化なしで機能するC標準に従って未定義の動作を呼び出す何かをしている可能性がありますが、GCCが最適化を実行するための特定の仮定を行うと、それらの仮定が真でない場合にコードが壊れます。-Wall必ずオプションを指定してコンパイルしてください-Wextra。これも良い考えであり、警告が表示されるかどうかを確認してください。-ansiまたはを試すこともできますが-pedantic、それらは誤検知を引き起こす可能性があります。

于 2008-11-11T05:16:42.860 に答える
5

実験として、これによりコンパイラがすべてを一貫して丸めるようになるかどうかを確認してください。

volatile float d1=distance(point,p1) ;
volatile float d2=distance(point,p2) ;
return d1 < d2 ;
于 2008-11-11T23:30:11.903 に答える
4

エイリアシングの問題が発生している可能性があります(または、他に何百万もの問題が発生している可能性があります)。-fstrict-aliasingオプションを検索します。

この種の質問は、より多くの情報なしでは適切に答えることは不可能です。

于 2008-11-11T05:16:33.750 に答える
3

コンパイラの障害が原因であることはめったにありませんが、コンパイラにはバグがあり、さまざまな最適化レベルで現れることがよくあります (たとえば、最適化パスにバグがある場合)。

一般に、プログラミングの問題を報告する場合: 問題を実証するための最小限のコード サンプルを提供し、ユーザーがコードをファイルに保存し、コンパイルして実行するだけで済むようにします。問題をできるだけ簡単に再現できるようにします。

また、さまざまなバージョンの GCC を試してみてください (独自の GCC をコンパイルするのは、特に Linux では非常に簡単です)。可能であれば、別のコンパイラで試してください。Intel Cには、多かれ少なかれGCCと互換性のあるコンパイラがあります(非商用利用は無料だと思います)。これは、問題を特定するのに役立ちます。

于 2008-11-11T07:05:13.297 に答える
2

それはほとんど(ほとんど)決してコンパイラではありません。

まず、-Wallを使用して、警告なしでコンパイルしていることを確認します。

それでも「エウレカ」の瞬間が得られなかった場合は、クラッシュする実行可能ファイルの最適化されていないバージョンにデバッガーを接続して、それが何をしているのか、どこに行くのかを確認します。

5を取得すると、この時点で問題が修正されたことがわかります。

于 2008-11-11T05:21:46.577 に答える
2

数日前に同じ問題が発生しました。私の場合はエイリアシングでした。また、GCCは、他のコンパイラーと比較した場合、それを異なる方法で実行しますが、間違いではありません。GCCは、C ++標準のルール弁護士と呼ばれるものになり、その実装は正しいですが、C ++でも本当に正しい必要があります。そうしないと、何かを過度に最適化することになり、これは苦痛です。しかし、あなたはスピードを得るので、文句を言うことはできません。

于 2008-11-11T05:27:40.793 に答える
1

コメントのいくつかを読んだ後、ここでいくつかの反対票を獲得することを期待していますが、コンソールゲームプログラミングの世界では、より高い最適化レベルが奇妙なエッジケースで誤ったコードを生成することがあることはかなり一般的な知識です。ただし、コードを微妙に変更することで、エッジケースを修正できる可能性があります。

于 2008-11-11T06:36:28.893 に答える
1

わかりました... これは私が今までに経験した中で最も奇妙な問題の 1 つです。
それが GCC のバグであると断言するのに十分な証拠があるとは思いませんが、正直に言うと... 本当にそのように見えます。

これは私のオリジナルの関手です。最適化のレベルなしで正常に動作し、最適化のレベルに関係なくセグメンテーション違反をスローするもの:

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 ;
    }
} ;

残念ながら、この問題は特定の値で発生するため再現が困難です。1,000 以上のベクトルのうちの 1 つだけをソートするとセグメンテーション違反が発生するため、各ベクトルが持つ値の特定の組み合わせに大きく依存します。

于 2008-11-11T23:07:30.420 に答える
0

うわー、こんなに早く答えが返ってくるとは思っていませんでした。

std::sort() を使用してポインターの std::vector をソートすると、エラーが発生します。

strict-weak-ordering ファンクターを提供します。

しかし、私が提供するファンクターが正しいことはわかっています。なぜなら、私はそれをたくさん使用しており、正常に動作するからです。

さらに、ベクトルをソートしたときにエラーが発生するため、エラーはベクトル内の無効なポインターではありません。最初に std::sort を適用せずにベクトルを反復すると、プログラムは正常に動作します。

GDBを使用して、何が起こっているのかを調べました。エラーは、std::sort がファンクターを呼び出すときに発生します。どうやら std::sort が無効なポインターをファンクターに渡しています。(もちろん、これは最適化されたバージョンでのみ発生し、あらゆるレベルの最適化 -O、-O2、-O3)

于 2008-11-11T05:45:46.297 に答える
0

更新された質問が示すように ;) 、問題はstd::vector<T*>. ベクトルに関する一般的なエラーの 1 つは、resize() されるはずのものを reserve() することです。その結果、配列の範囲外に書き込むことになります。オプティマイザーは、これらの書き込みを破棄する場合があります。

于 2008-11-11T13:41:20.173 に答える
0

本当の答えは、このスレッドのすべてのコメントのどこかに隠されています。まず第一に、これはコンパイラのバグではありません。

問題は浮動小数点の精度に関係しています。distanceToPointSort引数 (a,b) と (b,a) の両方に対して決して true を返さない関数にする必要がありますが、これは、コンパイラが一部のデータ パスにより高い精度を使用することを決定した場合に発生する可能性があることとまったく同じです。問題は特に可能性が高いですが、決して限定されません.x86なしで-mfpmath=sse. コンパレータがそのように動作する場合、sort関数が混乱する可能性があり、セグメンテーション違反は驚くべきことではありません。

ここで最善の解決策を検討-ffloat-storeします(CesarBによってすでに提案されています)。

于 2009-09-15T09:00:39.893 に答える
0

他の人が指摘したように、おそらく厳密なエイリアシングです。o3 でオフにして、もう一度やり直してください。私の推測では、インライン化テンプレート関数全体で失敗するファンクター (int 比較としての高速フロート? 下位 2 ビットのオブジェクト型?) でいくつかのポインター トリックを実行していると思われます。警告は、このケースをキャッチするのに役立ちません。「コンパイラがすべての厳密なエイリアシングの問題を検出できれば、それらを回避することもできます」無関係なコード行を変更するだけで、レジスタ割り当てが変更されるため、問題が発生したり消えたりする可能性があります。

于 2008-11-11T07:28:31.453 に答える
0

コードを遠くに投稿してください!おそらく何らかのポインター マジックを実行します。以前の投稿を参照してください。中間割り当てを行うと、レジスタの割り当てを変更してコードのバグを隠すだけです。これをさらに物語っているのは、出力が変化することです!

于 2008-11-12T04:10:07.110 に答える