33 人が閲覧(直近 30 日) 開発

セグメンテーションフォルト(Segmentation fault)の原因と調べ方|core dumpedの意味・gdb・ASan

セグメンテーションフォルト(Segmentation fault)の原因と調べ方|core dumpedの意味・gdb・ASan

「Segmentation fault (core dumped)」と表示されてプログラムが止まった場合、原因はほぼ必ず、プログラムが触ってはいけないメモリに触れたことです。この記事ではC/C++で起きる代表的な6つの原因をCのコード例で示し、gdb・AddressSanitizer(ASan)・dmesgを使って原因の行を突き止める手順をまとめます。PythonのC拡張で落ちた場合の調べ方も最後に扱います。

まとめ:Segmentation faultの原因と調べ方の要点

  • Segmentation faultは、OSがSIGSEGV(シグナル番号11)を送ってプロセスを止めた状態です。bashでは終了コードが128+11=139になります。
  • 代表的な原因は、NULLポインタ参照・文字列リテラルの書き換え・解放済みメモリへのアクセス・配列の範囲外アクセス・深すぎる再帰・未初期化ポインタの6つです。
  • 調べる順番は「-g -O0で再ビルド→警告を確認→gdbでbt→落ちないバグはASan」です。
  • コアファイルが見当たらないときは、ulimit -cと/proc/sys/kernel/core_patternを確認します。systemd環境ならcoredumpctlでコアを取り出せます。
  • 範囲外アクセスや解放後の使用は、落ちずに通ってしまうことがあります(未定義動作)。落ちなかったからといって正しいコードとは限りません。

Segmentation faultの意味とSIGSEGV・core dumpedの読み方

OSはプロセスごとに使ってよいメモリ範囲と、その範囲での読み書きの可否を管理しています。この範囲の外を読み書きしたり、読み取り専用の領域に書き込んだりすると、CPUの例外を受けたカーネルがプロセスにSIGSEGVを送ります。Linuxのsignal(7)では、SIGSEGVの説明は「Invalid memory reference」で、既定の動作はCore(コアダンプを出力して終了)です。シグナル番号はx86でもARMでも11です。「シグナル11で落ちた」というときのシグナル11も、このSIGSEGVです。

「Segmentation fault (core dumped)」の表示元と終了コード139

あのメッセージはプログラムが出力したものではありません。子プロセスがシグナルで終了したことを、親のシェルが終了ステータスから読み取って表示しています。bashのマニュアルには、番号Nの致命的シグナルで終了したコマンドの終了ステータスは128+Nと書かれているため、SIGSEGVなら139です。

$ gcc -g -O0 null.c -o null
$ ./null
Segmentation fault (core dumped)
$ echo $?
139

「(core dumped)」は、終了ステータスにコアダンプのフラグが立っていたときにシェルが付ける表示です。systemd-coredumpなどに渡した場合は保存設定によってファイルが残らないこともあるので、コアの有無は別に確認します。日本語ロケールで「Segmentation fault (コアダンプ)」と出るのは、glibcの日本語翻訳(ja.po、libc 2.36.9000版)では「Segmentation fault」が訳されずに原文のまま残り、bashの翻訳(5.2-rc1版)では「(core dumped)」が「(コアダンプ)」と訳されているためです。macOSのbashでは「Segmentation fault: 11」、zshでは「zsh: segmentation fault」と表記は異なりますが、どれも中身は同じSIGSEGVです。

セグフォ・セグメンテーション違反・セグメントエラーの呼び方の違い

「セグフォ」は略称で、「セグメンテーション違反」「セグメントエラー」は「segmentation fault」「segmentation violation」の訳語です。いずれも同じSIGSEGVを指すので、以降の調べ方はそのまま当てはまります。一方、「Bus error」「Aborted」は別のシグナルです。見分け方は後半の章で扱います。

Segmentation faultの主な原因6つとコード例

下の表は、各原因の最小コードをmacOS 26(Apple clang 21.0.0・x86_64・-O0)でビルドして実行したときの終了コードです。139はSIGSEGV、138はmacOSのSIGBUS(シグナル10)、0は落ちずに正常終了したことを表します。

原因 典型的な書き方 実行時の終了コード
NULLポインタの参照 int *p = NULL; *p; 139(SIGSEGV)
文字列リテラルの書き換え char *s = "hello"; s[0] = 'H'; 138(SIGBUS)
深すぎる再帰 終了条件のない再帰 139(SIGSEGV)
配列の範囲外アクセス for (i = 0; i <= 5; i++) a[i] 0(落ちない)
解放済みメモリへのアクセス free(buf);のあとにbufを読む 0(落ちない)
未初期化ポインタ int *p; *p = 1; 環境次第

NULLポインタの参照

mallocやfopen、自作関数がエラー時に返したNULLを確認せずに使ったときに起きます。C11の規格案N1570の6.5.3.2では、単項*でNULLポインタを参照したときの動作は未定義です。Linuxでは通常アドレス0付近が割り当てられていないため、多くの場合SIGSEGVで落ちます。ただし規格はこの結果を保証しておらず、最適化によって参照そのものが消えることもあります。

#include <stdio.h>
int main(void) {
    int *p = NULL;
    printf("%d\n", *p);   // NULLを参照してSIGSEGV
    return 0;
}

対策は、戻り値を使う直前にNULLかどうかを確かめることです。ポインタの基本的な扱いはポインタとは?C言語での使い方とメリット・言語別の違いをわかりやすく解説で整理しています。

文字列リテラルの書き換え

char *s = "hello";のsが指すのは文字列リテラルで、多くの環境では読み取り専用の領域に置かれます。N1570の6.4.5第7項は、文字列リテラルの配列を変更しようとしたときの動作を未定義としています。上の表のとおりmacOSではSIGBUSで落ちたので、同じバグでも届くシグナルはOSによって変わります。

char *s = "hello";   // 読み取り専用領域を指す
s[0] = 'H';          // 書き込みで落ちる

char t[] = "hello";  // 配列にコピーされるので書き換えられる
t[0] = 'H';

解放済みメモリへのアクセス(ダングリングポインタ)

freeしたあとに同じポインタを読み書きするバグです。実行した例では、解放後にprintf("%s\n", buf)しても落ちずに終了コード0で終わりました。解放された領域がまだプロセスのヒープとして割り当てられたままなので、OSからは正当なアクセスに見えたと考えられます。しばらくして別のmallocがその領域を再利用したとき、関係のない場所でデータが壊れます。

char *buf = malloc(16);
strcpy(buf, "issoh");
free(buf);
printf("%s\n", buf);   // 解放後の読み取り(落ちないことがある)
// 対策:free(buf); の直後に buf = NULL; を入れ、使う前に NULL かを確かめる

配列の範囲外アクセス

i <= 5のような境界の書き間違いで、要素5個の配列の6個目に書き込むバグです。実行した例では落ちずに終了コード0でした。書き込み先がたまたま割り当て済みのスタック領域だったため、OSが検知しなかったと考えられます。配列の配置によっては小さなはみ出しでも割り当てのないページに届いて落ちますし、壊れた値を後の処理が使って落ちることもあります。

int a[5] = {0};
for (int i = 0; i <= 5; i++) {   // i == 5 で a[5] に書き込む
    a[i] = i;
}

-Wall -Wextraを付けても、このループには警告が出ませんでした(clang 21で確認)。見つけるには、ASanや、UBSanの配列境界チェック(-fsanitize=bounds)が有効です。

再帰が深すぎることによるスタックオーバーフロー

関数を呼ぶたびにローカル変数がスタックに積まれ、スタックの上限を超えるとSIGSEGVになります。sigaltstack(2)には、標準のスタックを使い切ったときにカーネルがそのスレッドにSIGSEGVを送ると書かれています。メインスレッドのスタック上限はRLIMIT_STACKで決まり、ulimit -sで確認できます。man pageに載っている例は8192(8MiB)ですが、どの環境でもこの値に決まっているわけではありません。

int depth(int n) {
    char buf[1024];          // 1回の呼び出しで約1KBを消費
    buf[0] = (char)n;
    return depth(n + 1) + buf[0];   // 終了条件がない
}

大きな配列をローカル変数として宣言した場合も同じように落ちます。再帰の深さの見積もり方は再帰関数とは?自分を呼ぶ関数の仕組みとスタック・末尾再帰・実務判断を解説で扱っています。

未初期化ポインタとscanfの&忘れ

初期化していないポインタの中身は不定なので、どこを指しているか分かりません。初心者に多いのがscanf("%d", n)のように&を付け忘れるケースで、nの値(不定値)をアドレスとして書き込みます。こちらはコンパイラの警告で見つかります。clang 21に-Wall -Wextraを付けると、次の3件が出ました。

scanf.c:5:17: warning: format specifies type 'int *' but the argument has type 'int' [-Wformat]
scanf.c:5:17: warning: variable 'n' is uninitialized when used here [-Wuninitialized]
scanf.c:6:6: warning: variable 'p' is uninitialized when used here [-Wuninitialized]

落ちないメモリ破壊のほうが危ない理由

6つの原因のうち2つは、実行しても落ちませんでした。メモリの不正アクセスでSIGSEGVが届く代表例は、割り当てのないページや書き込み禁止のページに触ったときです(ほかに実行禁止領域の命令実行や、killでの明示的な送信もあります)。割り当て済みの領域の中での範囲外書き込みや、解放後もヒープに残っている領域の読み書きは、OSには検知できません。

この性質から、実務では次の2点を前提にしてください。

  • 落ちた行と原因の行は別のことが多い。ヒープを壊した処理はずっと前に終わっていて、壊れたデータを後から使ったmallocやfreeの中で落ちます。gdbのbtがlibcの内部を指していたら、この状況を疑います。
  • printfを足したら落ちなくなった、は直ったことにならない。コードを足すとスタックの配置や最適化の結果、実行のタイミングが変わり、壊れ方が表に出なくなっただけの可能性があります。調べようとすると消えてしまうこの種のバグは「ハイゼンバグ」と呼ばれます。-O0では動くのに-O2で落ちる場合も、最適化のバグではなく未定義動作を疑うのが先です。

こうした「落ちないバグ」を見つけるには、次の章のASanが有効です。ただしASanが検出できるのは、テストで実際に通った経路で、かつ検出対象の種類のエラーに限られます(構造体の隣のメンバーへのはみ出しなどは通常検出されません)。

Segmentation faultの原因の行を特定する手順

手順1:デバッグ情報付きの再ビルドと警告の確認

最適化を切り、デバッグ情報を付けてビルドし直します。最適化が効いていると、変数の値が<optimized out>と表示されて読めなくなります。

gcc -g -O0 -Wall -Wextra main.c -o main
clang --analyze main.c   # 実行せずに解析するclangの静的解析

clang --analyzeを今回の5本に当てると、NULL参照(Dereference of null pointer)と解放後使用(Use of memory after it is freed)の2件を指摘し、範囲外アクセスと文字列リテラルの書き換えは素通りしました。静的解析は実行前に拾えるパターンだけを拾うもので、見逃しがある前提で使います。仕組みとツールの選び方は静的解析とは?実行せずに欠陥を見つける仕組みとツールの選び方を参照してください。

手順2:gdbによるバックトレースの取得

gdbの上でプログラムを動かすと、SIGSEGVを受けた時点で止まり、「Program received signal SIGSEGV, Segmentation fault.」と表示されます。そこから次のコマンドで落ちた場所を調べます。gdbの最新版は2026年5月10日公開の17.2です。

$ gdb ./main
(gdb) run            # 引数があれば run arg1 arg2
(gdb) bt             # 呼び出し履歴(バックトレース)を表示
(gdb) frame 1        # btで確認した自分のコードのフレーム番号へ移動(1は例)
(gdb) info locals    # そのフレームのローカル変数
(gdb) print p        # ポインタの値。0x0ならNULL参照

btの先頭がlibcの関数(strlenやmemcpyなど)でも、下にたどると自分のコードのフレームがあります。そのフレームでprintし、渡した引数がNULLや解放済みのアドレスでないかを確かめます。macOSではgdbが同梱されていないので、lldb ./main→run→btと同じ手順で調べます。

手順3:コアダンプの取得と読み込み

再現に時間がかかるバグや本番環境で起きたクラッシュは、コアダンプから調べます。core(5)によると、RLIMIT_CORE(ulimit -c)が0だと通常のコアファイルは作られません。

ulimit -c unlimited                  # このシェルでコア出力を有効化
cat /proc/sys/kernel/core_pattern    # 出力先の設定を確認
gdb ./main core                      # コアファイルを読み込んで bt

core_patternが|で始まっている場合、コアはファイルとして保存されず、別のプログラムに渡されます。渡し先はsystemd-coredumpのほか、Apportや独自のハンドラのこともあります。systemdはkernel.core_patternを設定してsystemd-coredumpにコアを渡すので、カレントディレクトリを探しても見つかりません。渡し先がsystemd-coredumpなら、coredumpctl listで一覧を表示し、coredumpctl debugで直近のコアをgdbで開きます。

手順4:AddressSanitizerによる落ちないバグの検出

ASanはGCCとClangに組み込まれたメモリエラー検出器で、ヒープ・スタック・グローバル領域の範囲外アクセス、解放後使用、二重解放などを、実際に起きた時点で止めて報告します。

gcc -g -O0 -fsanitize=address -fno-omit-frame-pointer main.c -o main
./main

先ほど落ちなかった範囲外アクセスのコードを、ASanを付けて実行したときの出力の抜粋です。

==80257==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ff7b071c094 ...
WRITE of size 4 at 0x7ff7b071c094 thread T0
    #0 0x00010f7e38dc in main oob.c:5
    [32, 52) 'a' (line 3) <== Memory access at offset 52 overflows this variable

書き込んだ行(5行目)、はみ出した変数(3行目のa)、はみ出した位置まで分かります。[32, 52)はスタックフレーム内でaが占める範囲で、書き込み先のオフセット52は配列の先頭から20バイト先、つまり配列のすぐ後ろです。解放後使用のコードではheap-use-after-freeと、freeした行(7行目)が報告されました。Clangの公式文書は、ASanによる実行速度の低下を典型的には2倍としています。テスト用ビルドやCIで常時有効にし、リリースビルドには含めないのが一般的な使い方です。実行時のオプションは環境変数ASAN_OPTIONSで渡し、ASAN_OPTIONS=help=1で一覧を表示できます。

手順5:再ビルドできないバイナリのValgrind Memcheck

ソースコードがなくて再ビルドできないバイナリは、LinuxなどValgrindの対応環境で調べます(公式の対応範囲はmacOS 13までです)。valgrind ./mainで起動するだけで、不正な読み取りを「Invalid read of size 4」のように、読んだサイズとスタックトレース付きで報告します。最新版は2026年5月20日公開の3.27.1です。ASanより大幅に遅いので、再ビルドできるならASanを先に使ってください。

手順6:dmesgのsegfault行とerror番号の読み方

サーバー上のデーモンが黙って落ちたときは、カーネルログにSIGSEGVの記録が残っていることがあります。記録されるのはシグナルをプログラムが処理しなかった場合で、短時間に大量に起きたときは出力が間引かれます。dmesgかjournalctl -kで確認します。x86のarch/x86/mm/fault.cが出力する形式は次のとおりです。

プロセス名[PID]: segfault at 触ったアドレス ip 命令アドレス sp スタックアドレス error 番号 in 実行ファイル[...]

segfault at 0ならNULL参照です。errorの番号はページフォルトのエラーコードで、trap_pf.hの定義ではbit0が「0=ページなし/1=保護違反」、bit1が「0=読み取り/1=書き込み」、bit2が「0=カーネルモード/1=ユーザーモード」を表します。ユーザープログラムでよく見る値は次の4つです。

error 意味 疑う原因
4 読み取り・割り当てなし NULLや壊れたポインタの読み取り
5 読み取り・保護違反 読み取り禁止ページの読み取り
6 書き込み・割り当てなし NULLや壊れたポインタへの書き込み
7 書き込み・保護違反 読み取り専用領域への書き込み

非PIEの実行ファイルならipの値をそのまま、PIEや共有ライブラリならipからログ末尾の読み込み先頭アドレスを引いた値をaddr2line -e 実行ファイル アドレスに渡すと、ソースの行まで戻せます(デバッグ情報付きでビルドしている場合)。

SIGSEGVとBus error・Abortedの見分け方

メモリまわりのバグは、Segmentation fault以外の表示で落ちることもあります。表示されたメッセージから、どのシグナルが届いたかを判断します。終了コードはLinux x86での値です。

表示 シグナル 終了コード 主な原因
Segmentation fault SIGSEGV(11) 139 不正なアドレスの参照
Bus error SIGBUS(7) 135 ファイルの範囲外をmmap経由で参照など
Aborted SIGABRT(6) 134 ライブラリがメモリ破壊を検知して中断

free(): double free detectedやdouble free or corruption (!prev)、*** stack smashing detected ***: terminatedが表示されてからAbortedになるのは、glibcやコンパイラが挿入したチェックが破壊を検知し、自分でabort()を呼んだケースです。シグナルは違っても、原因はSegmentation faultと同じメモリ破壊なので、ASanで調べる手順はそのまま使えます。二重解放とメモリリークの関係はメモリリークとは?原因の特定方法と直し方を言語・環境別に解説で整理しています。

PythonでSegmentation faultが出たときの調べ方

Pythonのコードだけでは、通常SIGSEGVは起きません。落ちる原因は、C言語で書かれた拡張モジュール、ctypesで呼んだネイティブ関数、あるいはライブラリ自体の不具合です。標準ライブラリのfaulthandlerを有効にすると、落ちた時点のPythonのトレースバックが表示されます。

python -X faulthandler script.py
# または
PYTHONFAULTHANDLER=1 python script.py

公式ドキュメントの例では、「Fatal Python error: Segmentation fault」に続けて、スレッドごとに呼び出し中のPythonのファイル名と行番号が表示されます。最後に呼んだ拡張モジュールの関数が、疑うべき相手です。ライブラリ側の不具合なら、修正済みの版に上げるか、問題の出ていない以前の版に戻して固定します。たとえばpandas 3.0.4は2026年6月28日に公開されたあと、「Reported segfaults with datetime-related functionality」という理由でPyPIから取り下げ(yank)られました。アップデートした直後に落ち始めたら、使っているライブラリのリリースノートやissueを先に確認してください。

Segmentation faultの再発を防ぐビルド設定と書き方

  • テストはASan付きでビルドする。CIに-fsanitize=address,undefinedのビルドを1本追加すると、落ちない範囲外アクセスや解放後使用がテストの時点で止まります。入力のバリエーションまで広げるなら、ファジングとは?脆弱性を自動検出する仕組みとツール・他テストとの使い分け【2026年版】と組み合わせます。
  • 警告を無視しない。-Wall -Wextraで出た-Wformatや-Wuninitializedは、どちらもSIGSEGVに直結する警告です。-Werrorで警告をビルドエラーにするのも有効です。
  • freeの直後にNULLを代入する。free(NULL)は何もしないので、同じ変数をもう一度freeしても二重解放になりません。ただし、同じ領域を指す別のポインタには効かないので、所有者を1つに決めることと組み合わせます。
  • C++では生ポインタと生配列を避ける。所有権はstd::unique_ptrに持たせ、配列はstd::vectorにします。境界チェックが要る箇所では、範囲外でstd::out_of_rangeを投げるat()を使います。
  • 新規開発ならRustも選択肢にする。safe Rustでは、ダングリング参照や所有権の二重解放がコンパイルエラーになります。値の置き場所の考え方はRustのスタックとヒープ|値がヒープへ脱出する条件とメモリ領域の使い分けにまとめています。

よくある質問

Segmentation fault (core dumped) はどういう意味ですか?

プログラムが不正なメモリにアクセスしたため、OSがSIGSEGVで強制終了させ、コアダンプの処理が行われた、という意味です。コアファイルが実際に保存されたかどうかは、core_patternの設定によって変わるので別に確認します。表示しているのはプログラムではなくシェルで、bashでの終了コードは139です。

シグナル11(SIGSEGV)の原因は何ですか?

シグナル11はx86やARMのLinuxでのSIGSEGVの番号です。代表的な原因は、NULLポインタの参照、文字列リテラルの書き換え、解放済みメモリへのアクセス、配列の範囲外アクセス、深すぎる再帰、未初期化ポインタの6つです。

同じプログラムで落ちたり落ちなかったりするのはなぜですか?

範囲外アクセスや解放後の使用は、アクセス先がたまたま割り当て済みのページにあれば落ちないからです。入力やスタックの配置、最適化レベルが変わると落ちる場所が変わります。ASanを付けて実行すると、ASanの検出対象のエラーであれば、落ちないケースでも最初に検出した行で止まります。

コアダンプ(core)ファイルはどこに出力されますか?

/proc/sys/kernel/core_patternの設定で決まります。値が|で始まるならファイルではなく別のプログラムに渡されています。渡し先がsystemd-coredumpならcoredumpctl listで探せます。ulimit -cが0の場合、通常のコアファイルは作られません。

zsh: segmentation fault と表示されたときも同じ対処で良いですか?

同じです。zshやmacOSのbash(「Segmentation fault: 11」)は表記が違うだけで、届いているのは同じSIGSEGVです。macOSではgdbの代わりにlldbでrun→btを実行し、ASanはApple clangでも-fsanitize=addressで使えます。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.09.28 テックブログ タイムズカーの不正アクセスと約660万件の流出|免許証画像を退会者まで残さない保管設計
  2. 2026.09.25 コラム 最低賃金引き上げ【令和8年度】47都道府県の改定額・発効日と企業の対応手順
  3. 2026.09.25 コラム 障害者雇用の助成金一覧:月いくら・支給要件と申請書類を勤怠データで揃える方法
  4. 2026.09.05 コラム 犯罪収益移転防止法の本人確認:2027年4月の対面IC読み取り義務化と改修要件
  5. 2026.04.03 テックブログ マイナビ情報漏洩11万件|不正アクセスの経緯・対象確認と「登録は危険か」の判断材料

RELATED POSTS 関連記事

目次