エラーの解読
Segmentation fault (core dumped)ほど不可解なメッセージはそう多くありません。このエラーは、SIGSEGVシグナル(シグナル11)によって引き起こされることが多く、プロセスが自身に属さないメモリアドレスにアクセスしようとした際に発生します。Linuxカーネルが介入してシステムの整合性を保護するためにプロセスを強制終了し、分析用にメモリのスナップショット——「コアダンプ」——を保存します。
発生原因
メモリ違反のほとんどは、いくつかのコーディングミスに起因します。ロジックはさまざまですが、根本的なメモリエラーは通常、以下のいずれかのカテゴリに分類されます。
- **ヌルポインタの逆参照:**コードがアドレス
0x0への読み書きを試みています。 - **バッファオーバーフロー:**64バイトのバッファに128バイトを書き込み、隣接するスタックフレームを破壊しています。
- **スタックの枯渇:**深い再帰(例:10,000回以上の呼び出し)によって、スタックポインタがデフォルトの8MB制限を超えています。
- 解放後の使用:
free()やdeleteを呼び出した後にポインタにアクセスしており、そのポインタが不正なデータや別のオブジェクトを指している可能性があります。 - **ABIの不一致:**データ構造のオフセットが変更されたライブラリバージョンにリンクしています。PythonとNumPyのようなコンパイル済み拡張を混在させる場合によく発生します。
ステップ1:システムログを確認する
重いツールを起動する前に、カーネルリングバッファを確認しましょう。クラッシュが発生した場所の概要(命令ポインタ(IP)や問題のあるライブラリを含む)を提供してくれます。
sudo dmesg | tail -n 20
または、journalctlを使って最近の障害をフィルタリングします。
journalctl -xe | grep -i segfault
典型的なログエントリは次のようになります:segfault at 0 ip 00007f892c... sp 00007ffc... error 4 in libc-2.31.so。at 0はヌルポインタの逆参照を示し、libc-2.31.soは標準ライブラリの障害を指しています。
ステップ2:コアダンプを生成して分析する
多くの現代的なディストリビューションでは、ディスクの容量不足を防ぐためにコアダンプを無効にしています。クラッシュ状態を記録するには、この制限を手動で解除する必要があります。
まず、現在のシェル制限を確認します。
ulimit -c
出力が0の場合、現在のセッションでコアファイルのサイズ制限を無制限に設定します。
ulimit -c unlimited
プログラムを再度実行してください。クラッシュすると、coreまたはcore.PIDという名前のファイルが生成されます。このファイルを**GNU デバッガ(GDB)**に読み込んで、障害が発生した正確な行を確認します。
gdb ./your_binary core
GDBプロンプト内でbt(バックトレース)と入力すると、コールスタックが表示されます。これにより、障害に至るまでの関数呼び出しの順序がわかります。
ステップ3:Valgrindによる詳細なメモリ検査
GDBはプログラムがどこで終了したかを示しますが、Valgrindはなぜ終了し始めたかを示します。メモリの割り当てとアクセスをバイト単位で追跡する仮想CPUとして機能します。
パッケージマネージャを使ってインストールします。
sudo apt install valgrind # Debian/Ubuntu
sudo dnf install valgrind # Fedora/RHEL
Memcheckツールを通じてプログラムを実行します。
valgrind --leak-check=full ./your_binary
「Invalid write of size 8」や「Address 0x522d040 is 0 bytes inside a block of size 10 free'd」のようなレポートに注目してください。ValgrindはOSが違反を検知するよりもずっと前にメモリの破壊を検出します。
ステップ4:PythonのSegfaultのトラブルシューティング
純粋なPythonコードがセグメンテーション違反を起こすことはほとんどありません。もし発生した場合、TensorFlowやOpenCVのようなC拡張が原因であることがほとんどです。問題のある行を特定するには、組み込みのfaulthandlerモジュールを使用します。
メインスクリプトの先頭に以下の行を追加します。
import faulthandler
faulthandler.enable()
これにより、クラッシュが発生すると、Pythonはすべてのアクティブなスレッドのトレースバックをコンソールに直接出力し、壊れたCコードを呼び出している具体的なPython関数を特定できます。
ステップ5:確認と予防
修正を適用したら、クラッシュが止まったからといって正常に動作していると思い込まないでください。以下の検証手順に従ってください。
- **デバッグシンボル付きでコンパイルする:**必ず
-gフラグを使用してください(例:gcc -g main.c)。シンボルがないと、デバッガはメモリアドレスをソースコードにマッピングできません。 - **AddressSanitizer(ASAN)を使用する:**C++の場合、
-fsanitize=addressを付けて再コンパイルします。Valgrindより高速で、オーバーフローをリアルタイムに検出します。 - 静的解析で検証する:
cppcheckやclang-tidyを実行してください。これらのツールは、コードを実行する前に潜在的なヌル逆参照を発見します。
ベストプラクティス
防御的プログラミングはセグメンテーション違反に対する最善の対策です。C++ではポインタを必ずnullptrで初期化してください。モダンC++では、std::unique_ptrのようなスマートポインタを活用してメモリの解放を自動化しましょう。配列を扱う場合は、開発中にstd::vector::at()を優先して使用してください。サイレントかつ危険なメモリ侵害を許すのではなく、範囲外例外をスローしてくれます。

