t-hom’s diary

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

VBA ReDimはなぜ最後の次元だけしか拡張できないのか。

多次元の動的配列を作ることがある。

コードはこういう感じだ。

Dim Arr() As Byte
ReDim Arr(1 To 3, 1 To 3)

最初のReDimは拡張というよりは初期化なので、これはうまくいく。

3×3のサイズの配列なので、イメージとしてはこんな感じだろうか。
f:id:t-hom:20151226004753p:plain

ところでこの配列、不思議なことに、下には拡張できるのに、右には拡張できない。

f:id:t-hom:20151226005354p:plain

コードで説明すると以下のとおりで、最後の次元以外は変更できないのだ。

Dim Arr() As Byte
ReDim Arr(1 To 3, 1 To 3)
ReDim Arr(1 To 3, 1 To 4)  'これはOK
ReDim Arr(1 To 4, 1 To 4)  'これはNG

なぜ最後の次元しか拡張できないのか。

さて、なぜ最後の次元しか拡張できないかを説明しよう。

多次元配列をメモリに入れた場合、メモリに行と列の概念は無いので、直列に格納される。
f:id:t-hom:20151226005944p:plain

勘の鋭い方はこの図だけですでに気づかれたかもしれない。

つまり、最終次元の拡張はすこぶる簡単で、単に下にデータを足してやれば良いだけなのだ。
f:id:t-hom:20151226010323p:plain

一方、最初の次元を変更しようと思ったら、下図のようにデータを挿入する必要がある。
f:id:t-hom:20151226010928p:plain

メモリには挿入という操作はなく、できるのは書き換えのみである。
全データをひとすづつコピーでずらしていけば挿入を実現できなくはないが、計算コストがかかりすぎるのでVBAにはその方法が用意されていないものと思われる。

コードで確かめてみる

実際メモリ上に配列がどう並ぶかは、以下のコードで調べることができる。

Sub 多次元配列の要素のアドレス()
    Dim Arr() As Byte
    
    '配列の初期化(3×3)
    ReDim Arr(1 To 3, 1 To 3)
    For i = 1 To 3
        For j = 1 To 3
            Debug.Print "Arr(" & j & "," & i & ") ->" & VarPtr(Arr(j, i))
    Next j, i
    
    Debug.Print String(20, "-")

    '配列の拡張(3×4)
    ReDim Arr(1 To 3, 1 To 4)
    For i = 1 To 4
        For j = 1 To 3
            Debug.Print "Arr(" & j & "," & i & ") ->" & VarPtr(Arr(j, i))
    Next j, i
End Sub

出力結果はこのようになった。この数値はアドレスを表すので、つまり最終次元の拡張では、単純に下にデータが足されただけであることが分かる。

Arr(1,1) ->149624016
Arr(2,1) ->149624017
Arr(3,1) ->149624018
Arr(1,2) ->149624019
Arr(2,2) ->149624020
Arr(3,2) ->149624021
Arr(1,3) ->149624022
Arr(2,3) ->149624023
Arr(3,3) ->149624024
--------------------
Arr(1,1) ->149624016
Arr(2,1) ->149624017
Arr(3,1) ->149624018
Arr(1,2) ->149624019
Arr(2,2) ->149624020
Arr(3,2) ->149624021
Arr(1,3) ->149624022
Arr(2,3) ->149624023
Arr(3,3) ->149624024
Arr(1,4) ->149624025
Arr(2,4) ->149624026
Arr(3,4) ->149624027

ちなみに2度目のReDimの際に大きく拡張すると、初回で確保した領域の下が空いていなかったりするので、連続して使える別のメモリ領域が確保される。

    '配列の拡張(3×4)
    ReDim Arr(1 To 3, 1 To 100)

出力するとこのとおり。

Arr(1,1) ->149622696
Arr(2,1) ->149622697
Arr(3,1) ->149622698
Arr(1,2) ->149622699
Arr(2,2) ->149622700
Arr(3,2) ->149622701
Arr(1,3) ->149622702
Arr(2,3) ->149622703
Arr(3,3) ->149622704
--------------------
Arr(1,1) ->92462152
Arr(2,1) ->92462153
Arr(3,1) ->92462154
Arr(1,2) ->92462155
Arr(2,2) ->92462156
Arr(3,2) ->92462157
Arr(1,3) ->92462158
Arr(2,3) ->92462159
Arr(3,3) ->92462160
Arr(1,4) ->92462161
Arr(2,4) ->92462162
Arr(3,4) ->92462163

2度目のReDim以降、メモリの位置が変化していることが分かる。

なお、ReDim Preserveとしても付けても古いメモリ領域がそのままキープされるわけではなく、新たに用意された領域にデータかコピーされる仕組みである。

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