t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA 変数とメモリの関係 ~ 値渡しと参照渡しをメモリの動きから理解する

今回は変数とメモリの関係について書こうと思う。
ちょっと「記号表」とかいろいろ難しいモノが登場するのだが、ちょっとわかりやすく説明するためにVBAを擬人化しておこうと思う。

コンパイラー君とインタープリターさんの紹介

まずあなたがVBAコードを書く。
f:id:t-hom:20161005213254p:plain

すると、1行確定させるごとにコンパイラー君が間違ってないかチェックする。
もし間違いがあったらその都度あるいは実行前にコンパイラー君に注意される。
f:id:t-hom:20161005213555p:plain

これがいわゆるコンパイルエラー。

問題なければ、コンパイラー君はPコードという中間コードを生成する。
f:id:t-hom:20161005213457p:plain

Pコードは我々が目にすることはないが、VBAコードを圧縮して実行効率をアップさせたようなものだと思えば良い。

これを今度はVBAインタープリターさんが一行ずつ読んで順次実行していく。
f:id:t-hom:20161005214343p:plain

もしエラーがあると途中で実行をやめてしまう。
f:id:t-hom:20161005214518p:plain

これがいわゆる実行時エラー。

さて、まとめるとVBAのプログラムはこのような連携で動いているということになる。
f:id:t-hom:20161005220233p:plain

ただし、コンパイラーが挟まると話がややこしくなるのでここでコンパイラー君とはお別れ。
以降はインタープリターさんが直接VBAコードを読むという設定で話を進める。

変数とメモリの関係

VBAで次のようなコードを書いたとする。

Dim a As Integer
a = 10

単純な変数宣言と値の代入であるが、これを実行したとき、内部では何が起きてるんだろうか。
まずインタープリターさんがメモリーから2バイト確保する。
f:id:t-hom:20161005221813p:plain

それからインタープリターさんは確保したメモリに初期値のゼロを格納し、変数名とアドレスの対応付けを「記号表」に書き込む。
f:id:t-hom:20161005222432p:plain
この記号表というのもメモリ上のどこかにあるのだが、インタープリターさんが管理しているので我々プログラマーは普段意識することはない。

さて、次がa=10。このときインタープリターさんは記号表からaを探し、その番地を確認する。
f:id:t-hom:20161005223344p:plain

そして、実際の番地に10を格納する。
f:id:t-hom:20161005223603p:plain

そして次のように色々な型で宣言すると、

Sub hoge()
    Dim a As Integer
    a = 10
    Dim b As Integer
    b = -200
    Dim c As Integer
    c = 30
    Dim d As Long
    d = 60000
    Dim e As Double
    e = 12.5
End Sub

メモリと記号表はこんな感じになる。
f:id:t-hom:20161005224206p:plain

ローカル変数の場合、スタックメモリという領域に保存されるのでメモリ番地は下から順に埋まっていくのが特徴である。

値渡し・参照渡しの意味をメモリの動作から考える

プロシージャ呼び出しの際にパラメーターを渡す場合、値渡しと参照渡しの二種類がある。
呼び出される側のプロシージャでByValキーワードを使えば値渡しとなり、何も書かないか、ByRefと書けば参照渡しになる。これもどういうことなのか図で説明しよう。

まずは値渡しのケース。

Sub プロシージャA()
    Dim a As Integer
    a = 10
    Call プロシージャB(a)
    MsgBox a
End Sub

Sub プロシージャB(ByVal x As Integer)
    x = x * 2
End Sub

この場合は単純に10という数値がプロシージャBに渡る。
f:id:t-hom:20161005233802p:plain

byValの場合、xのアドレスはaのアドレスとは別に用意されるので、当然プロシージャBでxの値を書き換えてもプロシージャAの変数aに影響はない。
f:id:t-hom:20161006000542p:plain

さて、ここでプロシージャBをByRefに書き換えてみる。

Sub プロシージャB(ByRef x As Integer)
    x = x * 2
End Sub

すると今度は値が直接渡されるのではなく、変数aのアドレス「2880018」が渡される。
f:id:t-hom:20161006000839p:plain

そしてxのアドレスが2880018となり、結果的にプロシージャAの変数aと同じアドレスを指すことになる。
f:id:t-hom:20161006001349p:plain

さあ、そこでxを書き換えるということはつまり、変数aを書き換えてるのと同じってこと。
f:id:t-hom:20161006001847p:plain

以上が値渡しと参照渡しの内部動作の違いである。

あとがき

今回はホントはオブジェクト変数とメモリの関係について書きたかったんだけれど、その前提として基本型変数でのメモリの動作を説明しておかないとなんのことかわからなくなるのでいろいろと書いてるうちに力尽きた。

オブジェクトの場合はまた今度にしよう。。

なお、厳密にいえば今回説明したメモリアドレスは実際は物理メモリアドレスではなく、OSによって管理されたプロセスごとの仮想メモリ空間のアドレスである。ツッコミがあるといけないので一応最後に補足しておいたが、これを語りだすとまた長くなるのでとりあえず便宜的にはメモリアドレスだと思ってもらって良い。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。