リストHOME  リストOpen Source

第2回VBAプログラム豆知識

概要 ~ スタックとは? ~

 皆さん、VBA開発を行っていて、以下のようなエラーの画面が表示された経験はありませんか?

スタックオーバーフロー

 スタックって何? って思われた方も多いのではないでしょうか。今日は、このスタックについて、お話してみたいと思います。スタックは、プログラムが動作する上で、非常に重要な概念ですので、これを機会にしっかりと理解しておいてください。


 このスタックは、簡単にいうと、プログラムの持っているメモリ領域の一部で、プログラムは、この領域を常に利用しながら動作しているのです。Windowsで動作しているプログラム(VBAで書いたプログラムもそうですし、Word、Excel、メールソフト etc.)は、全て以下のような4ギガのメモリ空間を保持しています。その中にスタックと呼ばれる領域が、存在しています。

プログラムコードのサンプル

 先に見せたエラー画面と、上図から、スタック領域には限りがあるということが分かると思います。従って、エラーの原因は、この限りのある境界を超えて、スタックにアクセスしたということです。これを『スタックのオーバーフロー』と言います。
※VBAでは、変数の桁あふれのことも、オーバーフローと言いますが、これは、プログラムの実行中に変数の取りうる境界値を超えたことを意味します。


 では、どういうプログラムを書くとこのようなエラーが発生するかを説明したいと思います。
以下が、その例です。


'再帰呼び出しによる、スタックオーバーフロー
Public Function StackOverFlow(ByVal a As Integer, ByVal b As Long)
    Dim v1 As Integer
    Dim v2 As Long
    
    'スタックオーバーフローを説明するために、適当に値を設定しています。
    '深い意味はありません。    
    v1 = 1
    v2 = 2
    
    '再帰呼び出しにより、スタックの上限を超えさせます。
    StackOverFlow v1, v2

    '戻り値に-1を設定する。
    StackOverFlow = -1

End Function

エクセルファイルのダウンロード


 これは、StackOverFlow関数の中で、StackOverFlow関数を呼んでしまっている実装になっていて、延々、StackOverFlow関数が呼ばれ続けてしまい、最終的に、スタック領域の上限を超えてしまう結果に陥ります。このことから、プログラムが動作するのに、スタック領域が利用されており、限りがあるということが分かると思います。
※ダウンロードしたファイルの、【オーバーフロー】ボタンを押すことで、StackOverFlow関数を呼び出せますので、体験してみてください。


 それでは、次に、このスタックが、プログラムが動作する為に、どのように、利用されているのかについて、説明したいと思います。先に結論を言うと、関数の引数や関数内で宣言されている変数などが、スタックに割当てられています。このことを確認するために、先ほどのプログラムをちょっと、以下のように変更してみます。これは、イミディエイト・ウインドウに、関数の引数 a, bと、関数内に宣言されているローカル変数(グローバル変数に対して) v1, v2が配置されている、メモリ上のアドレス値を出力しています。



'スタックオーバーフロー2
Public Function StackOverFlow2(ByVal a As Integer, ByVal b As Long)
    Dim v1 As Integer
    Dim v2 As Long

    '引数v1とv2が配置されているアドレスを出力します。
    Debug.Print "adr_a=" & VarPtr(a)
    Debug.Print "adr_b=" & VarPtr(b)
    
    '変数v1とv2が配置されているアドレスを出力します。
    Debug.Print "adr_v1=" & VarPtr(v1)
    Debug.Print "adr_v2=" & VarPtr(v2)

    'スタックオーバーフローを説明するために、適当に値を設定しています。
    '深い意味はありません。
    v1 = 1
    v2 = 2
    
    '再帰呼び出しにより、スタックの上限を超えさせます。
    StackOverFlow2 v1, v2

    '戻り値に-1を設定する。
    StackOverFlow2 = -1

End Function


 ダウロードした、ファイルの【オーバーフロー2】ボタンを押して、実行してみると、スタックがオーバーフローしますので、イミディエイト・ウインドウを確認してみてください。以下は一番下の出力をコピペしたものです。一番下ということは、スタックがオーバーフローする直前ということを意味しています。


adr_a=44892396
adr_b=44892400
adr_v1=44892230
adr_v2=44892224


 まず、これから分かることが一つあります。引数は、VBAの場合、右側から順番に、スタックに積まれることが分かります。積むという表現にヒントがあるのですが、スタックはメモリのアドレス値の、大きい側から、小さい側へ、順番に、値を割当てていく構造に設計されています(これがスタックたる所以です)。なので、メモリアドレスの大きい(adr_b)方が、先に積まれていたということになります。また、ローカル変数は、書いた順番に積まれています。

スタックの積まれ方


 それではVBAで利用できるスタックってどのくらいのサイズが確保されいるのでしょうか? Debug.Printで出力された最後の値と、最初の値を引き算すれば、およその値が分かりますね。ここでは、説明だけで、サイズについては、割愛させて頂きます。
※イミディエイト・ウインドウは表示できる出力に限りがあるので、最初のPrint文の実行結果が消えている可能性があります。その場合は、一番最初だけ、Print文の代用として、MsgBoxを使うなどしてください。


プロセスのメモリ空間

 スタックには、もう一つ大事なことがあります。関数の戻り先(アドレス)も、関数呼び出し時に、スタックに積まれています。関数Aから関数Bを呼んだとします。関数Bの処理が終わると、当然、関数Aの呼び出された位置にプログラムの実行が、戻らなければいけません。なので、どこに戻ればよいか分かるように、呼び出された位置のアドレス値を、スタックに積んでいるというわけです。


 ちなみに、関数の戻り値は、スタックに積まれていません。これは、CPUの汎用レジスタ、eaxに格納されています。また、現在のプログラム位置をプログラムカウンタ、スタックの位置をスタックポインタといって、これもレジスタに格納されています。CPUのレジスタについては、今回は、詳しく触れません。


 関数の処理が終了すれば、その関数に必要だった値は、スタックに不要になるので、スタックから除外(解放)されます。スタックのデータの流れは、後に積まれたものが、先に解放される流れになります。このフローを後入れ先出し(LIFO:Last In First Out)と呼びます。イメージ的には、音響のボリューム・インジケーターのような感じを想像すると良いかもしれません。

スタックのデータフロー


 VBAでプログラムするに当たり、スタックには以下のようなものが、積まれているということを、覚えておくと良いでしょう。プログラムの理解度がグッ高まると思います。



 ちなみに、文章中の大事なところは、太文字にしてあります。



第1回  <  >  第3回



ダウンロード

 以下から、今回の豆知識で紹介した内容のエクセルファイルがダウンロードできます。

サンプルのダウンロード



連絡先

 ご意見・ご要望等ありましたら、画面最下部のメールアドレスまでご連絡ください。




エクセルバックアップ・ページのフッター
管理者のメールアドレス