Segmentation fault(NULLポインタの参照)
NULL(何も指していないポインタ)の指す先を読み書きしようとしたときに発生するクラッシュです。Cで最も頻出する実行時エラーです。
エラーメッセージの読み方
Segmentation fault
(エラーメッセージなし)- これはプログラム自身の出力ではありません。プログラムは何も言わずに落ちています
Segmentation fault- シェルが検知した異常終了の種類 — OSが「不正なメモリアクセス」などを検出しプロセスを強制終了させました
見つけ方- このメッセージだけでは行番号すら分かりません。原因はコードを読んで特定するしかありません
このエラーが出る典型パターン
パターン1
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) { 5 int *p = NULL ; ^ 6 printf("%d\n", *p); 7 return 0; 8 }
NULLは「何も指していない」という印です。*pでその指す先を読もうとした瞬間、無効なアドレスへのアクセスとしてクラッシュします。
直し方: NULL を malloc(sizeof(int)) にします。
パターン2
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) { 5 int value = 7; 6 int *p = NULL ; ^ 7 *p = value; 8 printf("%d\n", *p); 9 return 0; 10 }
pがNULLのまま*p = valueで書き込もうとすると、読み込みと同様にクラッシュします。有効な変数のアドレス(&value)を指すようにする必要があります。
直し方: NULL を &value にします。
パターン3
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) { 5 int *p = malloc(sizeof(int)); 6 p = NULL ; ^ 7 *p = 1; 8 printf("%d\n", *p); 9 return 0; 10 }
せっかくmallocで有効な領域を確保していても、途中でpにNULLを代入し直すと、その後の*pは無効な参照になります。
直し方: NULL を p にします。
パターン4
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) { 5 int total = 0; 6 int *ptr = NULL ; ^ 7 total += *ptr; 8 printf("%d\n", total); 9 return 0; 10 }
ptrがNULLのまま*ptrを読み取ろうとするとクラッシュします。
直し方: NULL を malloc(sizeof(int)) にします。
パターン5
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) { 5 int *count = NULL ; ^ 6 *count = 10; 7 printf("%d\n", *count); 8 return 0; 9 }
書き込み先がNULLのままだと、代入しようとした瞬間にクラッシュします。
直し方: NULL を malloc(sizeof(int)) にします。
よくある誤解
NULLを代入した行では何も起きません。実際にそのポインタを*pや->で参照した瞬間にクラッシュします。関数がNULLを返す可能性がある場合(mallocの失敗など)、使う前にNULLチェックをしないと危険です。
実務での勘所
NULLポインタの参照が確実にクラッシュする理由は偶然ではありません。多くのOSは、アドレス0を含む先頭のメモリページを意図的に「どのプロセスにも割り当てない」ように設定しています。NULL(通常はアドレス0)を参照しようとすると、CPUのメモリ管理機構がこの未割り当て領域へのアクセスを検出してハードウェア例外を発生させ、OSがSIGSEGV(セグメンテーション違反)としてプロセスを止めます。これは事故を確実に検出できるようにするための意図的な安全装置で、他の任意の不正なアドレスよりもNULL参照のほうが確実にクラッシュしてくれるのはこのためです。
関連するエラー
- Floating point exception(整数のゼロ除算)int型を0で割ったときに発生するクラッシュです。
- 配列のループが1つずれて範囲外を読む・最後の要素が抜ける添字は0から要素数-1までという境界を1つ間違えると、最後の要素が抜けたり、配列の外を読んでしまったりします。