Segmentation fault(配列の範囲外への書き込み)
配列の宣言サイズを大きく超えた添字に書き込むと、プロセスに割り当てられていないメモリ領域に触れてクラッシュします。
エラーメッセージの読み方
Segmentation fault
(エラーメッセージなし)- これはプログラム自身の出力ではありません。プログラムは何も言わずに落ちています
Segmentation fault- シェルが検知した異常終了の種類 — OSが「不正なメモリアクセス」などを検出しプロセスを強制終了させました
見つけ方- このメッセージだけでは行番号すら分かりません。原因はコードを読んで特定するしかありません
このエラーが出る典型パターン
パターン1
1 #include <stdio.h> 2 3 int main(void) { 4 int scores[5] = {1, 2, 3, 4, 5}; 5 scores[100000000] = 99; ^ 6 printf("%d\n", scores[0]); 7 return 0; 8 }
配列の宣言サイズを大きく超えた添字に書き込むと、プロセスに割り当てられていないメモリ領域に触れてクラッシュします。
直し方: 100000000 を 0 にします。
パターン2
1 #include <stdio.h> 2 3 int main(void) { 4 int data[10]; 5 data[999999999] = 1; ^ 6 printf("%d\n", data[0]); 7 return 0; 8 }
少しだけ超えた添字は近くの別の変数を静かに破壊するだけでクラッシュしないこともありますが、大きく外れた添字ではOSがメモリ保護違反として検出しクラッシュさせます。
直し方: 999999999 を 0 にします。
パターン3
1 #include <stdio.h> 2 3 int main(void) { 4 int buffer[3]; 5 buffer[50000000] = 5; ^ 6 printf("%d\n", buffer[0]); 7 return 0; 8 }
配列が小さいほど、少し大きめの添字を渡しただけでもプロセスの外の領域に触れやすくなります。
直し方: 50000000 を 0 にします。
パターン4
1 #include <stdio.h> 2 3 int main(void) { 4 int arr[6] = {1, 2, 3, 4, 5, 6}; 5 arr[200000000] = 42; ^ 6 printf("%d\n", arr[0]); 7 return 0; 8 }
配列の宣言サイズを大きく超えた添字への書き込みは、プロセスの外の領域に触れてクラッシュします。
直し方: 200000000 を 0 にします。
パターン5
1 #include <stdio.h> 2 3 int main(void) { 4 int vals[4]; 5 vals[750000000] = 7; ^ 6 printf("%d\n", vals[0]); 7 return 0; 8 }
配列が小さいほど、大きくずれた添字でメモリ保護違反を起こしやすくなります。
直し方: 750000000 を 0 にします。
よくある誤解
「範囲外アクセスは必ずクラッシュする」わけではありません。少しだけ超えた場合は近くの別の変数を静かに破壊するだけでクラッシュしないこともあります(発見はむしろこちらの方が困難です)。大きく外れた添字の場合にのみ、OSがメモリ保護違反として検出しクラッシュさせます。
実務での勘所
クラッシュするかどうかの境目は、OSのメモリ保護が「ページ」という単位(x86系では通常4KB)でしか働かないことに関係しています。範囲外への書き込みが、たまたま同じページ内に確保されている他のデータの領域にとどまっていれば、OSはそれを異常として検出できず、静かに他の変数やデータ構造を破壊するだけで済んでしまいます。書き込みがページの境界を越えて、プロセスに割り当てられていない領域にまで達して初めて、OSがハードウェアの例外を検出してクラッシュさせます。