関数内のローカル変数のアドレスを返すと解放済み領域を指す
関数の中で宣言したローカル変数は、その関数を抜けると同時に領域が無効になります。そのアドレスをreturnで返すと、呼び出し元は既に無効な領域を指すポインタを受け取ります。
エラーメッセージの読み方
Segmentation fault
(エラーメッセージなし)- これはプログラム自身の出力ではありません。プログラムは何も言わずに落ちています
Segmentation fault- シェルが検知した異常終了の種類 — OSが「不正なメモリアクセス」などを検出しプロセスを強制終了させました
見つけ方- このメッセージだけでは行番号すら分かりません。原因はコードを読んで特定するしかありません
このエラーが出る典型パターン
パターン1
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int *makeValue(int v) { 5 int local = v; 6 int *heap = malloc(sizeof(int)); 7 *heap = v; 8 return &local; ^ 9 } 10 11 int main(void) { 12 int *p = makeValue(42); 13 printf("%d\n", *p); 14 return 0; 15 }
Segmentation fault
localは関数を抜けると同時に無効になるスタック上の変数です。そのアドレスを返すと、main側で別の関数(printf)を呼んだ瞬間に同じ領域が上書きされ、クラッシュします。malloc()でヒープに確保したheapは関数を抜けても無効になりません。
直し方: &local を heap にします。
広告
広告スロット(未設定)
パターン2
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int *makeCounter(int start) { 5 int count = start; 6 int *heap = malloc(sizeof(int)); 7 *heap = start; 8 return &count; ^ 9 } 10 11 int main(void) { 12 int *c = makeCounter(10); 13 printf("%d\n", *c); 14 return 0; 15 }
Segmentation fault
malloc()でヒープに確保した領域は関数を抜けても無効になりません。スタック上のローカル変数のアドレスとの決定的な違いです。
直し方: &count を heap にします。
パターン3
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int *makeFlag(int v) { 5 int flag = v; 6 int *heap = malloc(sizeof(int)); 7 *heap = v; 8 return &flag; ^ 9 } 10 11 int main(void) { 12 int *f = makeFlag(1); 13 printf("%d\n", *f); 14 return 0; 15 }
Segmentation fault
関数を抜けた直後は運よくクラッシュしないこともありますが、別の関数呼び出しを挟むと高い確率で同じ領域が上書きされます。
直し方: &flag を heap にします。
よくある誤解
このサイトの検証環境では、関数を抜けた直後にmain側で別の関数(printfなど)を呼ぶと、解放済みのはずのスタック領域がその呼び出しのフレームに上書きされ、参照した瞬間にクラッシュすることを確認しています。「無効なだけで動くこともある」と侮らず、関数外に値を残したい場合は、呼び出し元の変数のアドレスを渡すか、mallocでヒープに確保します。
まとめ
関数内のローカル変数のアドレスを返すと解放済み領域を指すは中級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。