Segmentation fault(再帰関数の終了条件忘れによるスタックオーバーフロー)
再帰関数に正しい終了条件(ベースケース)が無いと、関数呼び出しのたびにスタック領域が積み上がり続け、上限に達した時点でクラッシュします。
エラーメッセージの読み方
Segmentation fault
(エラーメッセージなし)- これはプログラム自身の出力ではありません。プログラムは何も言わずに落ちています
Segmentation fault- シェルが検知した異常終了の種類 — OSが「不正なメモリアクセス」などを検出しプロセスを強制終了させました
見つけ方- このメッセージだけでは行番号すら分かりません。原因はコードを読んで特定するしかありません
このエラーが出る典型パターン
パターン1
1 #include <stdio.h> 2 3 int factorial(int n) { 4 if (0 ) { ^ 5 return 1; 6 } 7 return n * factorial(n - 1); 8 } 9 10 int main(void) { 11 printf("%d\n", factorial(5)); 12 return 0; 13 }
if (0)は常に偽なので、終了条件が一度も成立せずn-1、n-2……と再帰が終わらなくなります。関数呼び出しのたびにスタックが積み上がり、上限に達するとクラッシュします。
直し方: 0 を n <= 1 にします。
パターン2
1 #include <stdio.h> 2 3 int sumTo(int n) { 4 if (0 ) { ^ 5 return 0; 6 } 7 return n + sumTo(n - 1); 8 } 9 10 int main(void) { 11 printf("%d\n", sumTo(5)); 12 return 0; 13 }
ベースケースの条件を書き忘れる(あるいは常に偽になる条件を書いてしまう)と、再帰は自然には止まりません。
直し方: 0 を n <= 0 にします。
パターン3
1 #include <stdio.h> 2 3 int power(int base, int exp) { 4 if (0 ) { ^ 5 return 1; 6 } 7 return base * power(base, exp - 1); 8 } 9 10 int main(void) { 11 printf("%d\n", power(2, 4)); 12 return 0; 13 }
呼び出しが数千〜数万回に達するまで正常に動いているように見えることもありますが、スタック領域はヒープよりずっと小さいため、いずれ必ず上限に達してクラッシュします。
直し方: 0 を exp <= 0 にします。
パターン4
1 #include <stdio.h> 2 3 int countdown(int n) { 4 if (0 ) { ^ 5 return 0; 6 } 7 return countdown(n - 1); 8 } 9 10 int main(void) { 11 printf("%d\n", countdown(5)); 12 return 0; 13 }
if (0)は常に偽なので終了条件が成立せず、再帰が終わらなくなります。
直し方: 0 を n <= 0 にします。
パターン5
1 #include <stdio.h> 2 3 int fib(int n) { 4 if (0 ) { ^ 5 return n; 6 } 7 return fib(n - 1) + fib(n - 2); 8 } 9 10 int main(void) { 11 printf("%d\n", fib(5)); 12 return 0; 13 }
フィボナッチ数列の再帰でも同様です。ベースケースが成立しないと呼び出しが終わりません。
直し方: 0 を n <= 1 にします。
よくある誤解
無限ループと違ってすぐには気づきにくいのが特徴です。呼び出しが数千〜数万回に達するまで正常に動いているように見えることもありますが、スタック領域はヒープよりずっと小さいため、いずれ必ず上限に達します。
実務での勘所
再帰のたびにスタックへ積まれるのは、ローカル変数だけでなく戻り先のアドレスや保存されたレジスタの値も含まれ、1回の呼び出しで数十〜数百バイトを消費します。Linuxの既定のスタックサイズは多くの場合8MB程度で、ヒープに比べるとかなり小さく、ulimit -sコマンドで確認・変更できます。末尾再帰(関数の最後で自分自身を呼ぶ形)であれば理論上はループに変換できますが、Cの規格はこの最適化を保証していないため、深い再帰が必要な処理は最初からループで書くほうが安全です。