Menu
Coddy logo textTech

セグメンテーション違反とは?

セグメンテーション違反(segmentation fault、segfault)とは、プログラムがアクセスを許されていないメモリを読み書きしようとしたときに起きるクラッシュです。ヌルポインタを通してアドレス0を読むのが典型例で、オペレーティングシステムが SIGSEGV シグナルでプログラムを停止させます。

執筆: Kevin Spektor, 共同創業者、CTO

更新日: 2026年9月24日

Cのプログラムが最初の行を表示した後、誰も書いた覚えのないメッセージを出して止まります。

#include <stdio.h>

int main(void) {
    int *score = NULL;

    printf("About to read the score\n");
    printf("Score: %d\n", *score);
    return 0;
}
About to read the score
bash: line 1: 64030 Segmentation fault: 11  ./seg

この出力はmacOSのbashのもので、64030はプロセスID、11はシグナル番号です。Linuxでは、同じクラッシュはたいてい Segmentation fault (core dumped) と表示されます。どちらの場合も、プログラムはスコアを表示していません。score にはアドレス0が入っていて、*score はそのアドレスのメモリを読むようプロセッサに求めますが、それはどのプログラムにも許されていないのです。

セグメンテーション違反はどのように起きるのか

どのプログラムも、それぞれ専用の仮想アドレス空間の中で動いています。これは巨大なアドレスの範囲で、オペレーティングシステムが少しずつ中身を割り当てていきます。OSは、プログラムのコード、グローバル変数、スタック、ヒープを、ページと呼ばれるブロック単位でこの範囲に対応付けます(ほとんどのx86のLinuxシステムでは4 KB、Appleシリコン搭載のMacでは16 KB)。ほとんどのアドレスは対応付けられないまま残され、ヌルポインタのバグを捕まえられるよう、アドレス0は必ずその中に含まれます。

  1. プログラムが、あるアドレスを読み書きする命令を実行します。ここでは *score の読み取り、つまりアドレス0です。
  2. プロセッサのメモリ管理ユニットが、ページテーブルでそのアドレスを調べます。そのページは対応付けられていないか、プログラムが読み取り専用のページに書き込もうとしています。
  3. プロセッサは命令を止め、ページフォールトとしてカーネルに制御を渡します。
  4. カーネルは、そのアクセスが正当なものになり得るか(たとえば、スタックを拡張する必要があるだけか)を確認します。正当ではないので、カーネルはプロセスにシグナル11、SIGSEGV を送ります。
  5. SIGSEGV のデフォルトの動作は、プロセスを終了させ、システムが許可していればコアダンプを保存することです。その後シェルがメッセージを表示し、終了ステータスを139(128 + シグナル番号)に設定します。

つまりセグメンテーション違反はコンパイラが報告するものではなく、言語が発生させる例外でもありません。ハードウェアとカーネルがメモリを保護しているのであり、もっとも突然に起きる種類の実行時エラーです。Windowsは同じ現象を、例外コード 0xC0000005 のアクセス違反として扱います。

セグメンテーション違反のよくある原因

CとC++では、プログラムがどんなアドレスでも計算して使えるので、原因は結局、有効でないアドレスを使うことに行き着きます。

  • ヌルポインタの参照外し。 int *p = NULL; *p = 5; のようなコードです。malloc や fopen のように失敗すると NULL を返す関数は、結果をチェックしないとここにつながります。
  • 配列の末尾をはるかに超えたインデックス。 Cは境界をチェックしないので、arr[1000000] は単に100万要素先のアドレスになります。
  • free した後のメモリの使用。 ポインタには古いアドレスがまだ入っていますが、そのメモリはもうあなたのものではありません。
  • 初期化されていないポインタ。 int *p; *p = 5; は、p にたまたま入っているでたらめな値を通して書き込みます。
  • スタックオーバーフロー。 停止条件のない再帰関数は、スタックの末端を越えるまでスタックフレームを積み続けます。次の例は、macOSで Segmentation fault: 11 と終了ステータス139でクラッシュしました。
  • 文字列リテラルへの書き込み。 char *name = "coddy"; name[0] = 'C'; は読み取り専用のメモリを変更しようとします。Linuxではこれはセグメンテーション違反になります。macOSでは、同じプログラムが関連するシグナルである Bus error: 10 で停止しました。
#include <stdio.h>

int depth(int n) {
    return depth(n + 1) + 1;   /* never stops calling itself */
}

int main(void) {
    printf("%d\n", depth(0));
    return 0;
}

小さな誤りは、まったくクラッシュしないことがよくあり、そのほうが危険です。次のループは、要素が3つの配列の末尾を1つ超えて読み取ります。

#include <stdio.h>

int main(void) {
    int scores[3] = {72, 88, 95};
    int total = 0;

    for (int i = 0; i <= 3; i++) {   /* <= reads scores[3] */
        total += scores[i];
    }
    printf("Total: %d\n", total);
    return 0;
}

MacのClangでコンパイルすると、Total: 256 と表示されました。正しい合計は255です。scores[3] はスタック上の次の4バイトで、それはプログラム自身のものだったので、フォールトは起きず、でたらめな値が黙って足されました。オペレーティングシステムが止めるのは、プログラムがまったく所有していないメモリへのアクセスだけなのです。

どちらの誤りも、直し方は同じ習慣です。要素がいくつあるかを把握し、ポインタをたどる前にチェックすることです。

Best: 95
No scores, nothing to read

「core dumped」の意味

コアダンプとは、クラッシュした瞬間のプログラムのメモリのコピーを保存したファイルです。後でデバッガで開くと(gdb ./app core)、プログラムがどこにいたのか、変数に何が入っていたのかを正確に確認できます。多くのLinuxディストリビューションでは、systemd-coredumpがこれらのファイルを集めていて、coredumpctl list で一覧を見られます。たとえば ulimit -c 0 でコアダンプを無効にしていると、メッセージは括弧の中の言葉がない Segmentation fault だけになります。

クラッシュした行を見つける方法

プログラムがクラッシュした行が、バグのある行とは限りません。ポインタはある関数の中で無効になり、ずっと後に別の関数で使われることがあります。次のツールを使えば、その両方が分かります。

デバッガ。 デバッグ情報付きでコンパイルし、デバッガの中でプログラムを実行します。停止したら、bt(バックトレース)で関数呼び出しの連鎖がファイル名と行番号付きで表示されます。Linuxではたいてい gdb を使い、macOSでは lldb を使います。lldb でも bt は同じように動きます。

gcc -g app.c -o app
gdb ./app
(gdb) run
(gdb) bt

AddressSanitizer。 -fsanitize=address を付けてコンパイルし(GCCとClangの両方が対応しています)、プログラムを普通に実行します。するとただのセグメンテーション違反の代わりに、heap-use-after-free や stack-buffer-overflow のように誤りの種類を名指しし、アクセスした行とメモリを確保した行を示すレポートが表示されます。上で見た、それ自体では決してクラッシュしない、末尾を1つ超えた静かな読み取りも捕まえてくれます。

Valgrind。 Linuxでは、valgrind ./app で手を加えていないプログラムを実行し、Invalid read of size 4 のような不正な読み書きをすべて報告させられます。

ほかの言語のセグメンテーション違反

Python、Java、JavaScriptは、すべてのインデックスとすべての参照を使う前にチェックするので、同じ誤りは分かりやすいメッセージ付きの例外になります。Pythonなら IndexError や AttributeError、Javaなら ArrayIndexOutOfBoundsException や NullPointerException、JavaScriptなら TypeError です。これらは例外処理で捕捉できます。セグメンテーション違反はそうはいきません。これはシグナルであり、C++の catch ブロックからは見えないのです。

それでも、下で動いているCのコードが失敗すれば、Pythonのプログラムもセグメンテーション違反を起こすことがあります。次の行は ctypes にアドレス0を読むよう求めています。

import ctypes
ctypes.string_at(0)

python3 -X faulthandler で実行すると、Python 3.12は Fatal Python error: Segmentation fault と表示し、続けて実行中だったPythonの行を表示しました。C拡張やネイティブライブラリにメモリのバグがある場合も、同じクラッシュが起きます。Rustは別の方法を取っていて、無効なメモリにアクセスする可能性のあるコードのほとんどを、プログラムがビルドされる前にコンパイラが拒否します。

次に読むページ

Cのセグメンテーション違反のガイドでは、それぞれの原因を最小限のプログラムとその修正方法で順に説明しています。そもそも誤りを避けるには、ポインタ、ヌルポインタ、スタックとヒープについて読み、Cコースで練習してください。メモリをチェックしてくれる言語でエラーがどのように報告されるかは、実行時エラーのページを参照してください。

よくある質問

セグメンテーション違反はどうやって直せばいいですか?
まずクラッシュした行を突き止めます。-g を付けてコンパイルし、プログラムを gdb か lldb の下で実行して、クラッシュ後に bt と入力します。詳しいレポートがほしければ -fsanitize=address を付けてコンパイルします。次に、その行が使っているポインタやインデックスを直します。ポインタが NULL でないかをチェックする、配列のインデックスを長さ未満に収める、free した後のメモリを使わない、再帰が必ず終わるようにする、といった対処です。
セグメンテーション違反とメモリリークは同じものですか?
いいえ、両者は正反対の問題です。メモリリークは、プログラムが確保したメモリを解放しないままにすることで、プログラムは動き続けながら、どんどん多くのメモリを使っていきます。セグメンテーション違反は、プログラムが所有していないメモリへのアクセスで、プログラムはその場で停止させられます。解放した後のポインタを使うといった free の誤りはセグメンテーション違反の原因になり、free の書き忘れはリークの原因になります。
なぜ「セグメンテーション違反」という名前なのですか?
この名前はメモリのセグメンテーションという古い設計に由来します。プログラムのメモリは上限の決まったセグメントに分けられ、自分のセグメントの外のアドレスに触れることはフォールト(違反)でした。現代のシステムは代わりにページ単位でメモリを管理していますが、名前とシグナル名の SIGSEGV は残りました。Windowsでは同じ現象をアクセス違反と呼びます。
Pythonでもセグメンテーション違反は起きますか?
通常のPythonのコードでは起きません。Pythonはすべてのインデックスと参照をチェックし、代わりに例外を発生させるからです。それでも、Cのコードの内部ではPythonのプログラムもセグメンテーション違反を起こすことがあります。C拡張モジュール、機械学習フレームワークのようなライブラリ、ctypes などです。python -X faulthandler script.py で実行すると、クラッシュしたときに実行中だったPythonの行が表示されます。
終了コード139はどういう意味ですか?
終了コード139は、プロセスがシグナル11、つまりセグメンテーション違反を表す SIGSEGV によって強制終了されたという意味です。シェルは、シグナルによる終了を「128 + シグナル番号」として報告し、128 + 11 = 139 になります。DockerやKubernetesでコンテナが139で終了した場合、そのメインプロセスがセグメンテーション違反を起こしています。
Coddy programming languages illustration

Coddyでコードを学ぼう

始める