関数は自分で作る

スタック領域とは (深い話し)

上図のエラーメッセージを見たことがあるでしょうか?。「スタック領域が不足しています。」とは何なん?。再帰呼び出しのプロシージャを作らないと見ることも無いと思います。

以下のようなコードを標準モジュールかSheet1などのモジュールに書いaまたはbのプロシージャを実行すると上図が現れる。

Sub a()
    b
End Sub

Sub b()
    a
End Sub

この例では永遠に相手を呼び出すためスタックを使い果たす。で、「スタック領域」ですがC言語では説明が必ずあります。「ヒープとスタック」Googleですが、再帰呼び出しを理解する時に必要です。ヒープはある領域を確保、解放できる自由なメモリ領域。その領域をスタックとして使う。

別のコードを示す。mainを実行するとaプロシージャを呼び出し、aプロシージャのEnd Subを抜けると呼び出し元であるmainプロシージャに戻り、mainプロシージャのEnd Subで終了する。

Sub main()
  a 1, 2  
End Sub

Sub a(引数1 As Integer, 引数2 As Long)
  Dim ii As Long 'ローカル変数。aプロシージャ内で有効な変数。プロシージャレベルの変数
End Sub

プロシージャの呼び出しはスタック領域を使う決まりになっている。コンピュータの演算装置/CPUの仕組みとしてもスタックポインタという高速にメモリ番地を入れるレジスタ(箱のような物)がある。スタック領域は、メモリ上にある深い箱のようなものでメモリの番地を指す機能を持つ。スタック領域の構造は先入れ後出し法の深いツボの形Google。反対のキューの構造は、先入先出法のパイプの形。

プロシージャの呼び出しは、以下の2つのことをしてプログラムが動くことを気にして下さい。

  1. 実行位置をたどり、書いてあるコードを処理する。処理自体が実行位置を進むこと。
  2. 個々のプロシージャの処理コードは、あるメモリ領域にあり、プロシージャー間で呼び出す場合は、呼び出した所に戻るための位置や複数の引数、そのサイズをプロシージャーの定義(Sub/FunctionからEnd Sub/Functionのこと)、引数の通りにスタックに積み、呼び出されらたプロシージャーは、スタックから引数の順に取り出す。

Sub main()がaプロシージャを呼び出す時に、引数に1,2に渡すが渡すとはスタックに積むこと。引数1が引数2の半分の大きさにしているのは引数1はInteger型、引数2はLong型のため。さらにaプロシージャ内で宣言したii変数もスタック上に作られ、aプロシージャの処理が終わるとスタックポインタは「リターンアドレス」呼び出し元であるmainのコード上のaプロシージャを呼び出した位置(アドレス)に処理先を戻す。

前のコードの場合は、aプロシージャーを呼び出したらmainプロシージャーに戻るのでスタックオーバー(スタック領域が不足しています。)にはならない。

次のコードは、aプロシージャーは内部でaプロシージャーを呼び出す、「再帰呼び出し」をしている。aプロシージャーのコードは、aプロシージャーが終わる、抜ける分岐処理をしていないためスタック領域を使い果たす。

Sub main()
  a 1, 2
End Sub

Sub a(引数1 As Integer, 引数2 As Long)
  Dim ii As Long 'ローカル変数。aプロシージャ内で有効な変数。プロシージャレベルの変数
  a 11, 22 '自分自身を呼び出す。再帰呼び出しでスタックにデータは積むが取り出すことは無い
End Sub

再帰呼び出しは、同じ名前のプロシージャーだが引数やプロシージャー内でDim宣言した変数は、呼び出す度に違う引数、変数です。スタック上で使いますわす事は無い。これがあるから再帰呼び出しで同じ処理だが、引数や変数が違うのでそれぞれで適切な処理ができる。

ある仕事をする処理する単位は、プロシージャと言えます。クラスという巨大化した変数の前にプロシージャー間のインターフェースは知るべきです。ただ、RubyやPythonなどはクラスを作るところから始まることが多いが、RubyやPythonでもVBAで言うプロシージャが使えるだけで十分に仕事はできます。

クラスのプロパティやメソッドが使う側のインターフェースであるように、スタックは呼び出す側と呼び出される型のプロシージャとのインターフェースです。クラスうんぬんの前に知るべき機能です。

なお、プロパティやメソッドの呼び出しにもスタック領域は使います。

モバイルバージョンを終了