Segmentation fault(core dumped)
「Segmentation fault (core dumped)」で落ちる原因を頻度順に整理し、gdb・AddressSanitizer・core解析で「どの行のどのポインタか」まで即特定する手順を持ち帰れる。
- SIGSEGV は「プロセスにマップされていない/権限のないアドレスへCPUがアクセスした」ときにハードウェアのページフォルトからカーネルが送る同期シグナル。既定動作は終了+コア生成なので「(core dumped)」が付く。
- 原因の大半は NULL/解放済み/未初期化ポインタの参照、配列・バッファの範囲外、スタックオーバーフロー(深い再帰・巨大ローカル変数)、そしてサイズや型を取り違えたキャスト。
- gdbとbacktraceで落ちた行とフレームを特定し、再現できればAddressSanitizerで解放済み参照や範囲外を行単位で調べる。コアが出ないならulimitとcore_patternを確認する。
一言でいうと(このエラーが何を意味するか)
Segmentation fault (core dumped) は、プロセスが 自分に許可されていないメモリ番地へアクセスした 瞬間に強制終了された、という意味です。「セグメンテーション」はメモリ保護の単位(区画)を指し、その境界を踏み越えたので「フォルト(違反)」です。
仕組みはハードウェア主導です。CPU の MMU(メモリ管理ユニット)は、仮想アドレスを物理アドレスへ変換する際にページテーブルを引きます。目的のページが「未マップ」または「その操作(読み/書き/実行)を許可していない」場合、CPU は ページフォルト例外 を起こしてカーネルへ制御を渡します。カーネルはそのアクセスが正規(例:まだ物理割り当てされていないヒープへの初回書き込み=正常なデマンドページング)か不正かを判定し、不正なら当該プロセスへ SIGSEGV(シグナル番号11)を送ります。SIGSEGV の既定動作は「終了+コアダンプ」なので、末尾に (core dumped) が付きます。
プロセスがOSごと巻き込んで固まらず、当該プロセス「だけ」が落ちるのは、仮想メモリ保護が正しく働いている証拠です。各プロセスは独立したアドレス空間を持ち、他プロセスやカーネルの領域には触れられません。だからこそ不正アクセスは「静かなデータ破壊」ではなく「その場で SIGSEGV」という検出可能な形で表面化します。仮想メモリとページフォルトの原理は OS を参照してください。
横にスクロール
よくある原因(複数を具体的に、頻度順)
SIGSEGV は症状であり、真因はポインタ/メモリの扱いのバグです。実務での遭遇頻度が高い順に挙げます。
| 原因 | 何が起きているか | 典型的なコード上の兆候 |
|---|---|---|
| NULLポインタ参照 | アドレス0(近傍)へのアクセス。0ページは意図的に未マップ | malloc/fopen の戻り値を確認せず使用、未設定のコールバック呼び出し |
| 解放済み/ダングリング参照 | free 後や関数を抜けた後のポインタを使用(use-after-free) | free(p) 後の *p、ローカル変数のアドレスを返す関数 |
| 配列・バッファ範囲外 | 確保領域の外を読み書き。隣接領域や未マップページに到達 | off-by-one、strcpy でのバッファ溢れ、負や過大なインデックス |
| スタックオーバーフロー | スタック上限を越えてガードページに到達 | 終了条件のない再帰、巨大な自動配列、深い相互再帰 |
| 型・サイズの取り違え | 実体と異なる型で解釈しポインタ値を捏造 | 不正なキャスト、32/64bit のポインタ幅取り違え、共用体の誤読 |
補足すると、NULL参照 はアドレス0に近い小さな値へのアクセスとして現れます(p->field で p が NULL なら、field のオフセット分だけ0から離れた番地になる)。解放済み参照 が厄介なのは、解放直後はメモリがまだ読めてしまい、別の確保で上書きされて初めて壊れる点で、落ちる場所と真因が離れます(詳細は セキュリティ 側の use-after-free の解説と重なります)。スタックオーバーフロー は末尾で触れる「ガードページ」により SIGSEGV として検出されます。型の取り違え は、ポインタでない値をポインタとして参照した結果、たまたま未マップ番地を指して落ちます。
範囲外アクセスや解放済み参照は、踏んだ番地がまだ自プロセスにマップされていれば SIGSEGV を出さずに素通り します。その場で落ちないと、破壊されたデータが後段の無関係な処理を壊し、再現性のないバグになります。「落ちなかった=正しい」ではありません。だからこそ次章の AddressSanitizer で「アクセスした瞬間」に検出することが重要です。
診断の手順(切り分け方・見るべきログ/コマンド)
方針は一つ、「どのアドレスへ・どの命令で・どのコールスタックで」触れたか を特定することです。上から順に当たります。
1. まずコアダンプを出せる状態にする
コアが出ないと「(core dumped)」表示があっても解析できません。多くの環境では ulimit -c が0で抑止されています。
ulimit -c # 0 なら抑止されている
ulimit -c unlimited # このシェルから起動するプロセスでコア生成を許可
cat /proc/sys/kernel/core_pattern # 出力先。先頭が | なら apport/systemd-coredump 経由
core_pattern がパイプ(|)で始まる環境(Ubuntu の apport、systemd の systemd-coredump)では、カレントディレクトリに core は落ちません。coredumpctl list / coredumpctl gdb <PID> で取り出します。コアが出ない三大要因(RLIMIT_CORE、setuid によるダンプ抑止、ハンドラ登録)は OS のコアダンプ解説の切り分けがそのまま使えます。
2. gdb で落ちた行とバックトレースを見る
コアがあれば、実行ファイルと突き合わせて事後解析(ポストモーテム)します。デバッグ情報付き(-g、最適化は -O0 か -Og)でビルドしてあると行番号まで出ます。
gdb ./myapp core # または coredumpctl gdb myapp
(gdb) bt # バックトレース:死んだ関数の呼び出し履歴
(gdb) frame 2 # 疑わしいフレームへ移動
(gdb) info locals # そのフレームの局所変数
(gdb) print p # ポインタ値を確認。0x0 なら NULL 参照が濃厚
(gdb) info registers # フォルトを起こした命令とアドレス
bt の最上段が自分のコードでなく libc 内(strlen や memcpy)を指すことは珍しくありません。その場合、呼び出し元フレーム で渡したポインタが不正、と読み替えます。コアがなくても gdb --args ./myapp <引数> → run で、落ちた瞬間に自動で停止します。
3. 再現できるなら AddressSanitizer で真因の行を暴く
gdb が示すのは「落ちた場所」であって、必ずしも「壊した場所」ではありません。AddressSanitizer(ASan) は解放済み参照・範囲外・スタックオーバーフローを、壊した瞬間の行付きで報告します。
# GCC/Clang 共通。コンパイルとリンク両方に -fsanitize=address を付ける
cc -g -fsanitize=address -fno-omit-frame-pointer prog.c -o prog
./prog
# 例: heap-use-after-free / stack-buffer-overflow と、
# "freed by" と "allocated by" のスタックが両方出る
ASan は確保領域の周囲に「レッドゾーン」を置き、解放したメモリを一定時間隔離するため、素通りしていた不正アクセスを確実に捕まえます。テストや CI で回すのが定石です。ビルド手順やツールチェーンの前提は プログラミング 側の解説と合わせて押さえてください。
4. カーネル側のログ(dmesg)で客観的事実を得る
アプリのログが残らない突然死でも、カーネルはフォルトを記録していることがあります。
dmesg | grep -iE 'segfault|general protection|BUG'
# 例: myapp[12345]: segfault at 0 ip 000055... sp 000... error 4 in libc...
at 0 はアクセスした仮想アドレス(0=NULL 参照)、ip は命令ポインタ、error はフォルト種別のビットです。at が非常に大きい/中途半端な値なら、型の取り違えやポインタ破壊で捏造された番地を疑います。
- 落ちた行を知りたい → gdb で
bt - 「なぜそのポインタが壊れたか」を知りたい → AddressSanitizer(解放元・確保元が両方出る)
- コアが出ない →
ulimit -cとcore_pattern(パイプなら coredumpctl) - アプリのログが無い突然死 →
dmesgの segfault 行(アクセス番地と ip が分かる)
解決と予防(対処と再発防止)
診断で真因の種類が分かれば、対処は原因ごとに定石があります。
- NULL/解放済み:ポインタを使う前に妥当性を保証する。
malloc・fopen・find系の戻り値は必ず確認し、freeの直後にp = NULL;を代入して二重解放と再利用を早期にSIGSEGV化する。所有権を明確にし、C++ なら生ポインタよりunique_ptr/shared_ptrで寿命を型に語らせる。 - 範囲外:境界を計算に頼らず表現する。C++ では生配列より
std::vectorと.at()(範囲チェック付き)、固定長バッファへのコピーはstrncpy/snprintfで上限を渡す。長さとバッファを常に一組で扱う。 - スタックオーバーフロー:再帰に必ず終了条件と深さの上限を設け、深くなり得るなら明示スタックによる反復へ書き換える。巨大なローカル配列はヒープへ移す。
- 型・サイズの取り違え:安易なキャストを避け、
intptr_t/size_tなど幅の正しい型を使う。共用体は「今どのメンバが有効か」をタグで管理する。
人間の注意力に頼らないのが要点です。(1)CI に AddressSanitizer / UndefinedBehaviorSanitizer ビルドのテストを組み込み、不正アクセスをコミット時点で落とす。(2)コンパイラ警告を -Wall -Wextra で有効化し、-Werror で放置を防ぐ。(3)valgrind でリークと不正アクセスを補完的に検出する。(4)本番はデバッグ情報を分離保存(split debug info)し、コアと突き合わせて事後解析できる状態にしておく。テストと自動化の設計指針は DevOps を参照。
最後に一段で。Segmentation fault (core dumped) は失敗ではなく 防御機構の作動音 です。仮想メモリ保護が不正アクセスをその場で顕在化させてくれたおかげで、gdb でフレームを、AddressSanitizer で壊した行を特定できます。落ちた場所を直すのではなく、ポインタとメモリの所有権・寿命・境界という真因を直す——それが再発しない唯一の道です。
エラー辞典の記事ガイド
Segmentation fault(core dumped)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
SIGSEGV
比較で見る軸
難易度: advanced / カテゴリ: エラー辞典 / タグ数: 6
導入後に効く点
原因の大半は NULL/解放済み/未初期化ポインタの参照、配列・バッファの範囲外、スタックオーバーフロー(深い再帰・巨大ローカル変数)、そしてサイズや型を取り違えたキャスト。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- エラー辞典
- タグ数
- 6
判断チェックリスト
- 自社の用途が「SIGSEGV / メモリ」に近いか確認する。
- 強みである「SIGSEGV は「プロセスにマップされていない/権限のないアドレスへCPUがアクセスした」ときにハードウェアのページフォルトからカーネルが送る同期シグナル。既定動作は終了+コア生成なので「(core dumped)」が付く。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。