【C言語】第5章第14回:関数とスタックの関係
関数を呼び出す際、スタックがどのように操作されるかを理解することは、プログラムの仕組みを深く知るために重要です。この章では、関数とスタックの関係を詳しく解説します。
1. スタックとは?
1.1 スタックの概要
スタックは、データを一時的に保存するためのメモリ領域で、LIFO(Last In, First Out)方式でデータを管理します。
1.2 スタックの特徴
- LIFO構造:最後に追加されたデータが最初に取り出される。
- メモリ効率:一時的なデータ保存に適している。
- 動的な成長:関数の呼び出しに応じてスタックフレームが追加される。
2. 関数呼び出し時のスタック操作
2.1 スタックフレームの構造
関数が呼び出されるたびに、以下の情報を含むスタックフレームが作成されます:
- 引数:関数に渡されたデータ。
- ローカル変数:関数内で定義された変数。
- リターンアドレス:関数の終了後に戻るべきアドレス。
2.2 実行の流れ
1. 関数が呼び出される。
2. スタックフレームが作成され、引数とローカル変数がプッシュされる。
3. 関数が終了すると、スタックフレームがポップされる。
4. リターンアドレスに制御が戻る。
2.3 簡単な例
#include <stdio.h>
void funcB(int b) {
printf("In funcB, b = %d\n", b);
}
void funcA(int a) {
printf("In funcA, a = %d\n", a);
funcB(a + 1);
}
int main() {
funcA(5);
return 0;
}
解説:
main()
関数が呼び出され、スタックにそのフレームが作成されます。funcA()
が呼び出されると、新しいスタックフレームが追加されます。funcB()
が呼び出されると、さらにスタックフレームが追加されます。- 各関数の終了時にスタックフレームがポップされます。
3. スタックオーバーフローとは?
3.1 スタックオーバーフローの原因
スタックオーバーフローは、スタックの容量を超えるデータを保存しようとした場合に発生します。主な原因は以下の通りです:
- 無限再帰:関数が終了条件なしに再帰を繰り返す。
- 大きすぎるローカル変数:スタックに収まりきらないサイズの変数を宣言する。
3.2 防止策
- 再帰関数を適切に設計する。
- 必要に応じて動的メモリ確保(
malloc
など)を使用する。
4. 練習問題
以下の課題に挑戦して、スタックと関数の関係を深く理解しましょう。
- 2つの関数を相互に呼び出すプログラムを作成し、スタックフレームの動きを追ってください。
- 再帰関数を使用して階乗を計算し、スタックフレームの変化を観察してください。
- スタックオーバーフローを引き起こすコードを作成し、どのように発生するかを確認してください。(注意して実行してください!)
5. 練習問題の解答と解説
問2の解答
#include <stdio.h>
// 再帰的に階乗を計算する関数
int factorial(int n) {
if (n == 0 || n == 1) {
return 1;
}
return n * factorial(n - 1);
}
int main() {
int num = 5;
printf("Factorial of %d is %d\n", num, factorial(num));
return 0;
}
解説:このプログラムでは、再帰呼び出しごとに新しいスタックフレームが作成されます。
6. まとめ
関数とスタックの関係を理解することで、プログラムの動作を深く知ることができます。次回は、大規模なプロジェクトでの関数設計について学びます。