C ++でのセグメンテーション違反の修正


95

私はWindowsとUnix用のクロスプラットフォームC ++プログラムを書いています。ウィンドウ側では、コードは問題なくコンパイルおよび実行されます。Unix側ではコンパイルされますが、実行しようとするとセグメンテーション違反が発生します。私の最初の予感は、ポインタに問題があるということです。

セグメンテーション違反エラーを見つけて修正するための良い方法は何ですか?

回答:


134
  1. を使用してアプリケーションをコンパイルすると-g、バイナリファイルにデバッグシンボルが含まれます。

  2. gdbgdbコンソールを開くために使用します。

  3. それを使用fileして、コンソールでアプリケーションのバイナリファイルを渡します。

  4. runアプリケーションを開始するために必要な引数を使用して渡します。

  5. セグメンテーション違反を引き起こすために何かをしてください。

  6. コンソールに入力btしてgdbセグメンテーション違反のスタックトレースを取得します。


gコンテキストでコンパイルすることはどういう意味CMakeですか?
Schütze

2
デバッグビルドタイプを有効にします。1つの方法はcmake -DCMAKE_BUILD_TYPE=Debugです。
アントニンデシモ

36

クラッシュ自体が問題の本当の原因ではない場合もあります。おそらく、メモリが以前の時点で破壊されましたが、破損が現れるまでにしばらく時間がかかりました。ポインタの問題(配列境界チェックを含む)のチェックがたくさんあるvalgrindをチェックしてください。クラッシュが発生した行だけでなく、問題どこから始まるかがわかります。


19

問題が発生する前に、可能な限り回避するようにしてください。

  • できるだけ頻繁にコードをコンパイルして実行します。障害のある部分を見つけやすくなります。
  • 低レベル/エラーが発生しやすいルーチンをカプセル化して、メモリを直接操作する必要がほとんどないようにしてください(プログラムのモデル化に注意してください)
  • テストスイートを維持します。現在機能しているもの、機能しなくなったものなどの概要を把握しておくと、問題がどこにあるかを把握するのに役立ちます(ブーストテストは可能な解決策であり、私自身は使用しませんが、ドキュメントはどのような種類かを理解するのに役立ちます情報を表示する必要があります)。

デバッグには適切なツールを使用してください。Unixの場合:

  • GDBは、プログラムがクラッシュした場所を教えてくれ、どのような状況であるかを確認できます。
  • Valgrindは、多くのメモリ関連のエラーを検出するのに役立ちます。
  • GCCではmudflapを使用することもできます。GCC、Clangでは10月以降、実験的にMSVCを使用してアドレス/メモリサニタイザーを使用できます。Valgrindが検出しないエラーを検出でき、パフォーマンスの低下が少なくなります。-fsanitize=addressフラグと一緒にコンパイルして使用します。

最後に、いつものことをお勧めします。プログラムが読みやすく、保守しやすく、明確できれいであればあるほど、デバッグが最も簡単になります。


5

Unixではvalgrind、問題を見つけるために使用できます。それは無料で強力です。自分でやりたい場合は、newanddelete演算子をオーバーロードして、0xDEADBEEF新しいオブジェクトの前後に1バイトの構成を設定できます。次に、各反復で何が起こるかを追跡します。これはすべてをキャッチできない可能性があります(これらのバイトに触れることさえ保証されていません)が、過去にWindowsプラットフォームで機能しました。


1
これは1バイトではなく4バイトになります...しかし、原則は問題ありません。
Jonas Wagner


頑張れ。私たちはここで他の人を助けることを目的としているので、助けることができるものはすべて追加する必要があります。
ウィーティーズ2010

オーバーロードnewしてdelete非常に便利な場合-fsanitize=addressがありますが、コンパイラは問題のランタイム検出でコンパイルし、メモリを自動的に画面にダンプしてデバッグを容易にするため、使用することをお勧めします。
タリックウェリング

3

はい、ポインタに問題があります。適切に初期化されていないものを使用している可能性が非常に高いですが、メモリ管理をダブルフリーなどで台無しにしている可能性もあります。

初期化されていないポインタをローカル変数として回避するには、できるだけ遅く宣言してみてください。意味のある値で初期化できる場合は、できれば(常に可能であるとは限りません)。コードを調べて、使用する前に値があることを確認してください。それが難しい場合は、nullポインタ定数(通常はNULLまたはとして記述0)に初期化し、チェックしてください。

初期化されていないポインターをメンバー値として回避するには、それらがコンストラクターで適切に初期化され、コピーコンストラクターと代入演算子で適切に処理されることを確認してください。init他の初期化は可能ですが、メモリ管理の関数に依存しないでください。

クラスにコピーコンストラクターや代入演算子が必要ない場合は、それらをプライベートメンバー関数として宣言し、定義することはできません。明示的または暗黙的に使用されている場合、コンパイラエラーが発生します。

該当する場合は、スマートポインタを使用してください。ここでの大きな利点は、それらに固執して一貫して使用すると、書き込みdeleteを完全に回避でき、二重に削除されるものがないことです。

Cスタイルの文字列と配列の代わりに、可能な限りC ++文字列とコンテナクラスを使用してください。境界チェックを強制するため、.at(i)ではなくを使用することを検討してください[i][i]少なくともデバッグモードで、コンパイラまたはライブラリが境界をチェックするように設定できるかどうかを確認します。セグメンテーション違反は、完全に適切なポインタにガベージを書き込むバッファオーバーランによって引き起こされる可能性があります。

これらのことを行うと、セグメンテーション違反やその他のメモリの問題の可能性が大幅に減少します。彼らは間違いなくすべてを修正するのに失敗するでしょう、そしてそれはあなたが問題がないとき時々valgrindを使うべきである理由です、そしてあなたがそうするときvalgrindとgdbを使うべきです。


1

私はこのようなことを修正するために使用する方法論を知りません。あなたのプログラムの振る舞いが未定義であるというまさに当面の問題のためにそれを思い付くことが可能であるとは思わない(SEGFAULTが何らかのUBによって引き起こされていない場合は私にはわからない) 。

問題が発生する前に回避するためのあらゆる種類の「方法論」があります。重要なのはRAIIです。

それに加えて、あなたはそれにあなたの最高の精神的エネルギーを投げる必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.