再帰呼び出しの落とし穴

注意: この記事は1年以上前に掲載されたものです。情報が古い場合がありますのでお気を付け下さい。

再帰呼び出しといえば、プログラミングでもしばしば使われる技法であるが、これは一般的なループ以上に気をつけなければならない側面もある。ここでは、その落とし穴について書いてみたい。

まず、再帰呼び出しとは、ある関数がその関数を呼び出すというものである。今日では主流のプログラミング言語であれば、その多くが再帰呼び出しが使えるようになる。

例えば、以下のSwiftコードでは再帰呼び出しを使っている。

func countDown(_ num: Int) {
    print(num)
    if num > 0 {
        countDown(num - 1)
    }
}

countDown(10)

上記のコードはcountDown(_:)関数で10から0までの数字をカウントダウン方式で出力しているものであるが、変数barが0より大きければ、bar - 1で再度countDown(_:)をコールしている。最終的にはbarが0になるので、最終的には処理を抜ける。

とはいえ、再帰呼び出しはできればあまり多用はしないほうが良いという側面もある。というのは、言語にもよるが、関数を呼び出すときは、その関数の情報を保持していなければならないため、その情報がスタックされる。スタックできる情報には限りがあるので、呼び出した関数が深くなれば深くなるほど情報が増えていき、最終的にはスタックがあふれてしまい、アプリクラッシュが発生する。これを「スタックオーバーフロー」という。

例えば、先ほどのコードの場合は再帰呼び出しの代わりに、forループが使える。例えば、Swiftで書いた場合は以下のようになる。

func countDown(_ num: Int) {
    for i in (0...num).reversed() {
        print(i)
    }
}

countDown(10)

これであれば、再帰呼び出しをしないため、関数をスタックするということがないため、スタックオーバーフローの問題が起きることはない。

再帰呼び出しは便利ではあるが、思わぬ落とし穴があるため、使う際は慎重にいきたいところである。

タイトルとURLをコピーしました