Excelが大好きだ!

Excel大好き経理マンがExcelの事を書き綴っていきます。


スポンサードリンク

Excel VBAのオブジェクト型変数の生成から破棄まで 2.見直し編

前回はオブジェクト変数についての自分の勘違いしていた内容をまとめてみました。

www.excellovers.com

今回は色々な方にアドバイスを頂いて自分の中で理解できた内容をまとめてみようと思います。

検証用コード

前回も記載したコードですが再掲いたします。

StudentクラスにClass_Terminateを追加しています。
それ以外は前回と同じです。

Studentクラス

'### Studentクラス
Private mName As String

Public Property Let Name(ByVal vName As String)
     mName = vName
End Property

Public Property Get Name() As String
     Name = mName
End Property

Sub Class_Terminate()
     Debug.Print "破棄"
End Sub

標準モジュール

Sub オブジェクト参照テスト()
     Dim 生徒A As Student
     Set 生徒A = New Student
     Dim 名簿Col As Collection
     Set 名簿Col = New Collection
     
     名簿Col.Add 生徒A
     生徒A.Name = "Kou"
     Debug.Print 名簿Col(1).Name
     
     Set 生徒A = Nothing
     Debug.Print 名簿Col(1).Name
End Sub

上記のコードを実行すると下記のようにイミディエイトウィンドウに表示されます。
f:id:ExcelLover:20211216171336j:plain


前回の勘違い編では、2回めのDebug.Printの直前でset 生徒A = nothingをしているにも関わらず、'Kou'と表示されるのが理解できていませんでした。

今回の見直し編では、なぜ2回目のDebug.Printが正常に動作するのか解明したいと思います。


オブジェクト型変数の生成

オブジェクト型変数はInteger型・String型とはデータの扱い方が異なっています。

メモリのスタックメモリとヒープメモリ

メモリはスタックメモリとヒープメモリに分けられます。

オブジェクト型以外の変数はスタックメモリに直接保存されます。

オブジェクト型はオブジェクト本体はヒープメモリに、本体への参照情報がスタックメモリに保存されます。

スタックメモリへの変数確保

VBAでは『Dim XXX as 変数のデータ型』と記載することでスタックメモリへ変数が確保されます。

この時に変数を確保するために使用されるメモリは下記のようになっています。

データ型 記憶領域サイズ 範囲
Boolean 2 バイト True または False
Byte 1 バイト 0 ~ 255
Currency (スケーリングされた整数) 8 バイト -922,337,203,685,477.5808 ~ 922,337,203,685,477.5807
Date 8 バイト 100 年 1 月 1 日 から 9999 年 12 月 31 日
Double (倍精度浮動小数点数) 8 バイト -1.79769313486231E308 から -4.94065645841247E-324 (負の値) 4.94065645841247E-324 から 1.79769313486232E308 (正の値)
Integer 2 バイト -32,768 〜 32,767
Long (Long 整数) 4 バイト -2,147, 483,648 〜 2,147, 483,647
Single (単精度浮動小数点数) 4 バイト -3.402823E38 から -1.401298E-45 (負の値) 1.401298E-45 から 3.402823E38 (正の値)
String (可変長) 10 バイト + 文字列の長さ 0 〜 約 20 億
文字列型 (String) (固定長) 文字列の長さ 1 〜 約 65,400
バリアント型 (Variant) (文字) 22 バイト + 文字列長 (64 ビット システムでは 24 バイト) 可変長 文字列型 と同じ範囲
Object 4 バイト 任意の Object 参照

データ型の概要 | Microsoft Docs より抜粋 docs.microsoft.com

上記表でBoolean型からString型の範囲は具体的な変数へ代入できる数値・文字数が記載されています。
Object型に関しては『任意のObject参照』と記載されているだけです。

String型などの変数には値そのものが代入されています。 しかしObject型は代入されているのはオブジェクト本体そのものではなく、オブジェクト本体への参照情報が代入されています。


昔から大きなデータを扱うオブジェクトでも4バイトしか使用しないのはなぜなのかと思っていました。

Object型変数は参照情報しか持っていないため、扱っているデータ量に関わらず一定のメモリしか使用せずに済むのですね。


オブジェクト型変数への代入

f:id:ExcelLover:20211216171504j:plain

Object型変数への代入は『set XXX = New クラス名』で行います。

まず『New クラス名』でクラスからオブジェクトを作成します。
クラスはオブジェクトの設計図。
クラスを元に実際にインスタンス(実体化・作成)されたものがオブジェクトです。

『set XXX』では作成されたオブジェクトへの参照情報を代入します。

Object型変数の場合、ヒープメモリに確保されているオブジェクトの本体と、スタックメモリに確保されているオブジェクト本体への参照情報の2つの情報が存在します。

表中のオレンジの線はオブジェクト変数がオブジェクト本体を参照している事を表します。

この参照設定が残っていることがオブジェクトの破棄に関わってきます。

余談ですが、with句を使用することもオブジェクト本体への参照を行っていることになります。



f:id:ExcelLover:20211216171625j:plain

同じように名簿Collectionも作成します。

ユーザー定義クラスでも既存のクラスでも同様に参照情報とオブジェクト本体の2つの情報が存在します。

オブジェクト変数を渡す

f:id:ExcelLover:20211216171713j:plain

名簿Colに生徒Aを追加します。

この時に気をつけないといけないことは、ヒープメモリに確保されているオブジェクト本体ではなく、スタックメモリに確保されているオブジェクト本体への参照情報が追加されるということです。

オブジェクト変数のプロパティを実行

f:id:ExcelLover:20211216171754j:plain

Collectionオブジェクト内にコピーされた「生徒A」は「生徒A2」と表現しています。

生徒A.Name = 'Kou'」を実行すると、生徒Aが参照している変数本体に対して値が追加されます。

生徒Aは参照情報のみを保持しますので、それ以外の情報を直接保持しません。

この時点でStudentオブジェクト本体は2つのオブジェクト変数から参照されています(矢印2つ)。

この時点で「Debug.Print 名簿Col(1).Name」を実行する。

Collectionオブジェクト内の「生徒A2」の参照情報を通じてStudentオブジェクト本体からNameの情報を抽出します。


オブジェクト変数の破棄

f:id:ExcelLover:20211216171833j:plain

オブジェクト変数は使用後に破棄をする必要があります。

「set 生徒A = Nothing」の部分がそうです。

しかしこのコードで破棄されるのは生徒Aというオブジェクト本体への参照部分のみになります。

この時点ではオブジェクト本体はヒープメモリ内に確保されたままになります。


オブジェクト本体はオブジェクト変数から参照されなくなった時に自動で削除されます。

Studentオブジェクト本体は「生徒A」からの参照が無くなっても、Collectionオブジェクト内の「生徒A2」から参照されているため削除されません。

そのため2回めの「Debug.Print 名簿Col(1).Name」は生徒Aが削除されても実行できます。

Collectionオブジェクト内の生徒A2を通じてStudentオブジェクトからNameの情報を抽出しています。


前回の勘違い変では生徒Aというオブジェクト変数自体がオブジェクト本体で、Nameの情報を持っていると思っていたため、動作を正しく理解することが出来ていませんでした。

最後にプロシージャを終了する時にCollectionオブジェクトが破棄されます。

その結果Collectionオブジェクト内の生徒A2も破棄され、Studentオブジェクトへの参照情報が無くなり、Class_Terminateが発動します。

まとめ

Object型変数は変数自体には参照情報しか持っていないということが、他の型の変数と違うところで大きく勘違いしていたことでした。

この知識を持って自作のコードを見直してみたいと思います。

※言うまでもないですが、自分なりの解釈ですので事実とは異なる可能性があります

参照情報

色々と教えてくださってありがとうございます。

thom.hateblo.jp

きっかけとなった書籍