多次元の動的配列を作ることがある。
コードはこういう感じだ。
Dim Arr() As Byte ReDim Arr(1 To 3, 1 To 3)
最初のReDimは拡張というよりは初期化なので、これはうまくいく。
3×3のサイズの配列なので、イメージとしてはこんな感じだろうか。
ところでこの配列、不思議なことに、下には拡張できるのに、右には拡張できない。
コードで説明すると以下のとおりで、最後の次元以外は変更できないのだ。
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
なぜ最後の次元しか拡張できないのか。
さて、なぜ最後の次元しか拡張できないかを説明しよう。
多次元配列をメモリに入れた場合、メモリに行と列の概念は無いので、直列に格納される。
勘の鋭い方はこの図だけですでに気づかれたかもしれない。
つまり、最終次元の拡張はすこぶる簡単で、単に下にデータを足してやれば良いだけなのだ。
一方、最初の次元を変更しようと思ったら、下図のようにデータを挿入する必要がある。
メモリには挿入という操作はなく、できるのは書き換えのみである。
全データをひとすづつコピーでずらしていけば挿入を実現できなくはないが、計算コストがかかりすぎるので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としても付けても古いメモリ領域がそのままキープされるわけではなく、新たに用意された領域にデータかコピーされる仕組みである。