C言語

【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. 練習問題

以下の課題に挑戦して、スタックと関数の関係を深く理解しましょう。

  1. 2つの関数を相互に呼び出すプログラムを作成し、スタックフレームの動きを追ってください。
  2. 再帰関数を使用して階乗を計算し、スタックフレームの変化を観察してください。
  3. スタックオーバーフローを引き起こすコードを作成し、どのように発生するかを確認してください。(注意して実行してください!)

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. まとめ

関数とスタックの関係を理解することで、プログラムの動作を深く知ることができます。次回は、大規模なプロジェクトでの関数設計について学びます。