nullptrの参照外しでSegmentation faultになる原因
nullptrで初期化したポインタをそのまま参照外し(*p)して書き込もうとしたときに異常終了するバグです。コンパイルは通り、実行時にクラッシュします。
エラーメッセージの読み方
Segmentation fault
(エラーメッセージなし)- これはプログラム自身の出力ではありません。プログラムは何も言わずに落ちています
Segmentation fault- シェルが検知した異常終了の種類 — OSが「不正なメモリアクセス」などを検出しプロセスを強制終了させました
見つけ方- このメッセージだけでは行番号すら分かりません。原因はコードを読んで特定するしかありません
このエラーが出る典型パターン
パターン1
1 #include <iostream> 2 int main() { 3 int *p = nullptr; 4 *p = 5; ^ 5 std::cout << *p << std::endl; 6 return 0; 7 }
nullptrはどのオブジェクトも指していません。そこへ書き込もうとするとOSが不正なメモリアクセスを検知してプロセスを強制終了させます。有効なint変数のアドレスをpに入れてから使う必要があります。
直し方: *p = 5; を int value = 5; p = &value; にします。
パターン2
1 #include <iostream> 2 int main() { 3 int *ptr = nullptr; 4 *ptr = 10; ^ 5 std::cout << *ptr << std::endl; 6 return 0; 7 }
エラーメッセージはプログラム自身の出力ではなく、シェルが検知した異常終了の種類を1行で報告しているだけです。原因はコードを読んで特定する必要があります。
直し方: *ptr = 10; を int v = 10; ptr = &v; にします。
パターン3
1 #include <iostream> 2 int main() { 3 int *data = nullptr; 4 *data = 99; ^ 5 std::cout << *data << std::endl; 6 return 0; 7 }
nullptrを「まだ何も指していない」という意味の安全な初期値として使うのは正しいですが、使う前に必ず有効なアドレスを代入する必要があります。
直し方: *data = 99; を int d = 99; data = &d; にします。
パターン4
1 #include <iostream> 2 int main() { 3 int *count = nullptr; 4 *count = 1; ^ 5 std::cout << *count << std::endl; 6 return 0; 7 }
countがnullptrのまま書き込もうとするとクラッシュします。有効な変数のアドレスを代入してから使う必要があります。
直し方: *count = 1; を int c = 1; count = &c; にします。
パターン5
1 #include <iostream> 2 int main() { 3 int *score = nullptr; 4 *score = 88; ^ 5 std::cout << *score << std::endl; 6 return 0; 7 }
nullptrは「何も指していない」という印にすぎません。使う前に必ず有効なアドレスを代入します。
直し方: *score = 88; を int s = 88; score = &s; にします。
よくある誤解
「nullptrは0と同じ意味だから安全」というのは誤解です。nullptrは「どのオブジェクトも指していない」ことを表すだけで、そこへの書き込みや読み込みは有効なメモリ領域を指していないため、OSに検知されクラッシュします。
実務での勘所
モダンC++では、そもそも生のポインタでnullptrを持ち回すこと自体を減らす方向に設計する習慣が広まっています。値が存在しないかもしれないことを表したいならstd::optional(C++17)、所有権を伴うオブジェクトの管理にはstd::unique_ptrやstd::shared_ptrといったスマートポインタを使えば、danglingや二重解放、参照外し忘れといった生ポインタにまつわる事故の多くを型の力で防げます。生のポインタは、所有権を持たない一時的な参照を表す場合など、限定的な用途にとどめるのが現在の実務での主流です。