● 構造体のサイズについて ●

不思議な、不思議な構造体サイズについてのお話。実は自分自身よく分かっていない。

今回使う構造体は以下の通り。

    Private Type udtTest
        strBuff1 As String
        strBuff2 As String * 20
        lngNum As Long
        strBuff3 As String
        intNum As Integer
    End Type

何の変哲も無い構造体である。

この構造体の各メンバのサイズ、構造体のサイズを求めると下のようになった。え゙〜っ!!て感じになるねこりゃ。だって、各メンバサイズを合計した値と構造体自体のサイズが違うんだもん。

 Len を使用LenB を使用
strBuff100
strBuff22040
lngNum 44
strBuff300
intNum 22
単純合計2646
実際の構造体サイズ 3454

Len、LenBそれぞれ各メンバサイズの単純合計実際の構造体サイズには8バイトのズレがある。問題の箇所は見れば一目瞭然!! 動的文字列変数 strBuff1 と strBuff3 だ。単純計算で2で割ると、構造体のサイズには4バイトとしてカウントされている。果たして、この4バイトはどこからきているの?4バイトのデータ型は、Long、Single、Object がある。まさか、オブジェクト型として保持されてはいないだろう。とすると Long か Single だ。

ここからは推測になっちゃうんだけど、4バイトの分は変数のアドレスだと思うんだよね。VB5.0以降でStrPtr、VarPtr、ObjPtrという隠し変数(VB4.0では VarPtr が用意されているが、使用するには宣言が必要)がありそれぞれ、文字列が存在するアドレス、変数のアドレス、オブジェクトのアドレスを返してくれるんだけど、戻り値はすべてLong型なんだよね。どうであろうか。

構造体で、文字列変数を使用する場合、固定長変数は確保したサイズ分をカウントし、可変長変数の場合はその変数が存在するアドレスの分(Long型=4バイト)をカウントするのではないだろうか。

さて、これが本当かどうか調べてみよう。CopyMemory API関数を使えばできそうである。早速、コーディング開始!!

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSource As Any, ByVal ByteLen As Long)

'----------------------------------------------------------------
' 関数名 : DumpMemInfo
' 機能   : 指定メモリー位置から指定サイズ分、1バイトずつ表示する
' 引数   : (in) gPtr … メモリー
'           (in) VarSize … サイズ
' 戻り値 : なし
'----------------------------------------------------------------
Public Sub DumpMemInfo(ByVal gPtr As Long, ByVal VarSize As Long)

    Dim i As Integer
    Dim byteBuff(0) As Byte

    For i = 0 To VarSize - 1
        Call CopyMemory(byteBuff(0), ByVal (gPtr + i), 1)
        Debug.Print Hex$(byteBuff(0)) & Space$(1);
    Next i

    Debug.Print ""

End Sub

関数は上の通りでよろしい。さて、実際に調査してみましょう。

Private Sub Command1_Click()

    Dim udtTest As TEST

    With udtTest
        .strBuff1 = "Test"
        .strBuff2 = "TEST"
        .lngNum = &HFCFDFEFF
        .strBuff3 = "てすと"
        .intNum = &HFEFF
    End With

    'String型メンバ変数のポインタを16進数表示する
    Debug.Print "? udtTest.strBuff1 : " & Right$("00000000" & Hex$(StrPtr(udtTest.strBuff1)), 8)
    Debug.Print "? udtTest.strBuff3 : " & Right$("00000000" & Hex$(StrPtr(udtTest.strBuff3)), 8)

    '構造体データを丸っとダンプする
    Call DumpMemInfo(VarPtr(udtTest), LenB(udtTest))

End Sub

こちらの環境で実行した結果、以下の通りになった。

 ? udtTest.strBuff1 : 001849AC
 ? udtTest.strBuff3 : 001767BC
 AC 49 18 0 54 0 45 0 53 0 54 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 FF FE FD FC BC 67 17 0 FF FE

上の値を変数(可変長文字列変数は4バイトと仮定)にしたがって分かりやすく分類してみると、

 AC 49 18 0
 54 0 45 0 53 0 54 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0 20 0
 FF FE FD FC
 BC 67 17 0
 FF FE

となる。あれっ? Long、Integer 変数に指定した値とメモリーに保持されている値、表示された可変長文字列変数のアドレスとメモリーに保持されているアドレスの値がひっくり返っているね。また、固定長文字列変数は思いっきり Unicodeとして保持されているし、はじめの4文字以降はきちんとChr$(0)で埋められている。非常に面白いね。これは覚えていても損はない知識だね。

さて、話を元に戻そう。上の数字を見ると先ほどの予想が当たっているね。え、何がって?だから、構造体の動的文字列変数は文字列が存在するアドレスを保持するということさ。例えば、? udtTest.strBuff1 : 001849AC と AC 49 18 0。ひっくり返ってはいるものの明らかにアドレスでしょこれは。従って、構造体において可変長文字列変数は4バイトとして扱われる、ということが言えるのではないか。


戻る