需求背景
C、C++和.net语言都提供了计算变量内存大小的函数即SizeOf,该函数能正确返回数据类型占用的内存字节数,但是VBA、VB6.0没有直接提供。特别在调用Windows API的时候,该功能显示得特别重要。
实现思路
VB6.0虽然没有提供指针运算符,但提供了几个获取变量指针(内存地址)的值的函数。
新建From1(窗体),新建t(TextBox),Command1、Command2(按钮CommandButton),代码:
运行结果:(&H是我后期添加的结论)
VarPtr(i(1)) = 142505232
VarPtr(i(2)) = 142505234
VarPtr(i(3)) = 142505236
VarPtr(i(4)) = 142505238
VarPtr(i(5)) = 142505240
----------
StrPtr(i(1)) = 142505180
StrPtr(i(2)) = 142505180
StrPtr(i(3)) = 142505180
StrPtr(i(4)) = 142505180
StrPtr(i(5)) = 142505180
----------
VarPtr(l(1)) = 377404000
VarPtr(l(2)) = 377404004
VarPtr(l(3)) = 377404008
VarPtr(l(4)) = 377404012
VarPtr(l(5)) = 377404016
----------
StrPtr(l(1)) = 142505180
StrPtr(l(2)) = 142505180
StrPtr(l(3)) = 142505180
StrPtr(l(4)) = 142505180
StrPtr(l(5)) = 142505180
----------
VarPtr(s(1)) = 377404192
VarPtr(s(2)) = 377404196
VarPtr(s(3)) = 377404200
VarPtr(s(4)) = 377404204
VarPtr(s(5)) = 377404208
----------
StrPtr(s(1)) = 379393388
StrPtr(s(2)) = 379393820
StrPtr(s(3)) = 379393892
StrPtr(s(4)) = 142505516
StrPtr(s(5)) = 142505068
----------
VarPtr(s1) = 1700212
VarPtr(s2) = 1700196
VarPtr(s3) = 1700180
VarPtr(s4) = 1700164
VarPtr(s5) = 1700160
----------
StrPtr(s1) = 146467388 &H8BAEA3C
StrPtr(s2) = 379394324 &H169D1914
StrPtr(s3) = 379366460 &H169CAC3C
StrPtr(s4) = 142506020 &H87E7824
StrPtr(s5) = 142504676 &H87E72E4
----------
__________________________________________________________________________________________
Start Address = &H8BAEA3C Number of Bytes = 32
Address: OS: Memory: ASCII:
08BAEA3C 0000 77 00 77 00 77 00 2E 00 6D 00 61 00 6E 00 6F w.w.w...m.a.n.o.
08BAEA4C 0010 6E 00 67 00 6B 00 75 00 2E 00 63 00 6F 00 6D n.g.k.u...c.o.m.
__________________________________________________________________________________________
__________________________________________________________________________________________
Start Address = &H169D1914 Number of Bytes = 32
Address: OS: Memory: ASCII:
169D1914 0000 47 00 6F 00 6F 00 64 00 21 00 1F 77 84 76 7D G.o.o.d.!..w.v}Y
169D1924 0010 E6 54 2E 00 00 00 00 00 00 00 00 00 57 81 02 .T..........W...
__________________________________________________________________________________________
__________________________________________________________________________________________
Start Address = &H169CAC3C Number of Bytes = 32
Address: OS: Memory: ASCII:
169CAC3C 0000 59 00 65 00 73 00 2E 00 00 00 44 00 4C 00 4C Y.e.s.....D.L.L.
169CAC4C 0010 00 00 00 00 06 00 00 00 08 00 00 00 3F 34 03 ............?4..
__________________________________________________________________________________________
__________________________________________________________________________________________
Start Address = &H87E7824 Number of Bytes = 32
Address: OS: Memory: ASCII:
087E7824 0000 4F 00 6B 00 61 00 79 00 2E 00 00 00 57 E0 E1 O.k.a.y.....W...
087E7834 0010 57 E0 E1 E4 5E E9 8F EC E4 43 72 47 F9 AB 23 W...^....CrG..#G
__________________________________________________________________________________________
__________________________________________________________________________________________
Start Address = &H87E72E4 Number of Bytes = 32
Address: OS: Memory: ASCII:
087E72E4 0000 48 00 69 00 2E 00 00 00 00 00 00 00 97 EA E1 H.i.............
087E72F4 0010 97 EA E1 E4 9E E3 8F EC 24 49 72 47 ED E7 23 ........$IrG..#G
__________________________________________________________________________________________
其它数据类型都可以通过这样的方式来计算出内存的大小,这样调用Windows API 就非常方便了。
关于 77 00 ,参考 《VB6 串口通信,发送正常,接收的数据总是不对,数据中多了几个 00》
上方如果
s1 = "www.manongku.com"
变成了
s1 = "我www.manongku.com"
那么汉字在内存中就是就是:
11 62 77 00 77 00 77 00 2E 00...
关于 62 11 = 我,参考《VB6 将Unicode转中文》
看来Intel系列采用小端序,把低位的11放在低端,把高位的62放到高端。
深入一下:
如果我们在代码
'后续结果随机
t.Text = t.Text & "StrPtr(s1) = " & StrPtr(s1) & vbCrLf
t.Text = t.Text & "StrPtr(s2) = " & StrPtr(s2) & vbCrLf
t.Text = t.Text & "StrPtr(s3) = " & StrPtr(s3) & vbCrLf
t.Text = t.Text & "StrPtr(s4) = " & StrPtr(s4) & vbCrLf
t.Text = t.Text & "StrPtr(s5) = " & StrPtr(s5) & vbCrLf
t.Text = t.Text & strLine
后修改s1、s2从而变成:
'后续结果随机
t.Text = t.Text & "StrPtr(s1) = " & StrPtr(s1) & vbCrLf
t.Text = t.Text & "StrPtr(s2) = " & StrPtr(s2) & vbCrLf
t.Text = t.Text & "StrPtr(s3) = " & StrPtr(s3) & vbCrLf
t.Text = t.Text & "StrPtr(s4) = " & StrPtr(s4) & vbCrLf
t.Text = t.Text & "StrPtr(s5) = " & StrPtr(s5) & vbCrLf
t.Text = t.Text & strLine
s1 = "你好www.manongku.com"
s2 = "Hiwww.qq.com"
'后续结果随机2
t.Text = t.Text & "StrPtr(s1) = " & StrPtr(s1) & vbCrLf
t.Text = t.Text & "StrPtr(s2) = " & StrPtr(s2) & vbCrLf
t.Text = t.Text & "StrPtr(s3) = " & StrPtr(s3) & vbCrLf
t.Text = t.Text & "StrPtr(s4) = " & StrPtr(s4) & vbCrLf
t.Text = t.Text & "StrPtr(s5) = " & StrPtr(s5) & vbCrLf
t.Text = t.Text & strLine
可以看到运行结果修改了的s1、s2变了地址,而没修改的s3、s4、s5地址不变。看来是CPU重新申请了地址。
StrPtr(s1) = 192503724
StrPtr(s2) = 63122836
StrPtr(s3) = 63122276
StrPtr(s4) = 191219644
StrPtr(s5) = 191219668
----------
StrPtr(s1) = 192506316
StrPtr(s2) = 63122476
StrPtr(s3) = 63122276
StrPtr(s4) = 191219644
StrPtr(s5) = 191219668
----------
显示试试数组:
s(1) = "www.manongku.com"
s(2) = "Good!真的好哦."
s(3) = "Yes."
s(4) = "Okay."
s(5) = "Hi."
t.Text = ""
t.Text = t.Text & StrPtr(s(1)) & vbCrLf
t.Text = t.Text & StrPtr(s(2)) & vbCrLf
t.Text = t.Text & StrPtr(s(3)) & vbCrLf
t.Text = t.Text & StrPtr(s(4)) & vbCrLf
t.Text = t.Text & StrPtr(s(5)) & vbCrLf & vbCrLf
s(1) = "你好www.manongku.com"
s(2) = "HiGood!真的好哦."
t.Text = t.Text & StrPtr(s(1)) & vbCrLf
t.Text = t.Text & StrPtr(s(2)) & vbCrLf
t.Text = t.Text & StrPtr(s(3)) & vbCrLf
t.Text = t.Text & StrPtr(s(4)) & vbCrLf
t.Text = t.Text & StrPtr(s(5)) & vbCrLf
运行结果也一样,修改了的字符串地址会变动,没修改的字符串不会变动。代码如下:
s(1) = "www.manongku.com"
s(2) = "Good!真的好哦."
s(3) = "Yes."
s(4) = "Okay."
s(5) = "Hi."
t.Text = ""
t.Text = t.Text & StrPtr(s(1)) & vbCrLf
t.Text = t.Text & StrPtr(s(2)) & vbCrLf
t.Text = t.Text & StrPtr(s(3)) & vbCrLf
t.Text = t.Text & StrPtr(s(4)) & vbCrLf
t.Text = t.Text & StrPtr(s(5)) & vbCrLf & vbCrLf
s(1) = "你好www.manongku.com"
s(2) = "HiGood!真的好哦."
t.Text = t.Text & StrPtr(s(1)) & vbCrLf
t.Text = t.Text & StrPtr(s(2)) & vbCrLf
t.Text = t.Text & StrPtr(s(3)) & vbCrLf
t.Text = t.Text & StrPtr(s(4)) & vbCrLf
t.Text = t.Text & StrPtr(s(5)) & vbCrLf
运行结果:
192499908
63992044
63992724
63991604
63992884
192504084
192902316
63992724
63991604
63992884
现在看下定长字符串:
如果把
Dim s(1 To 5) As String
改成
Dim s(1 To 5) As String * 100
那么运行结果就是不变:
192607964
192607964
192607964
192607964
192607964
192607964
192607964
192607964
192607964
192607964
对于数组肯定也是不变的,因为本身就是定长,代码如下:
t.Text = ""
t.Text = t.Text & VarPtr(i(1)) & vbCrLf
t.Text = t.Text & VarPtr(i(2)) & vbCrLf
t.Text = t.Text & VarPtr(i(3)) & vbCrLf
t.Text = t.Text & VarPtr(i(4)) & vbCrLf
t.Text = t.Text & VarPtr(i(5)) & vbCrLf & vbCrLf
i(1) = 6
i(2) = 7
i(3) = 3
i(4) = 4
i(5) = 5
t.Text = t.Text & VarPtr(i(1)) & vbCrLf
t.Text = t.Text & VarPtr(i(2)) & vbCrLf
t.Text = t.Text & VarPtr(i(3)) & vbCrLf
t.Text = t.Text & VarPtr(i(4)) & vbCrLf
t.Text = t.Text & VarPtr(i(5)) & vbCrLf
运行结果:
64083368
64083370
64083372
64083374
64083376
64083368
64083370
64083372
64083374
64083376
关于字符串数组占用内存的测试报告,请看《VB6 如何节约内存?Dim定义到过程当局部变量与全局的区别》。
对于变量,看下它在不同子程序中是否被复制?
Private Sub Command1_Click()
Dim arr(0) As Byte
arr(0) = 65
Debug.Print VarPtr(0)
Call s_1(arr)
End Sub
Private Sub s_1(arr() As Byte)
Debug.Print VarPtr(0)
Call s_2(arr)
End Sub
Private Sub s_2(arr() As Byte)
Debug.Print VarPtr(0)
End Sub
运行结果:
1700298
1700142
1699986
证明了,再次调用,测试局部变量或全局变量地址也会变,这样效率低。能节省s_1就不用s_1最快。
Private Sub Command1_Click()
Dim bArr() As Byte
ReDim bArr(10)
Debug.Print VarPtr(bArr(0))
ReDim Preserve bArr(10)
Debug.Print VarPtr(bArr(0))
ReDim bArr(20)
Debug.Print VarPtr(bArr(0))
ReDim Preserve bArr(20)
Debug.Print VarPtr(bArr(0))
End Sub
运行结果:
197909000
197909000
63627392
63627392
看代码:
Private Sub Command1_Click()
Dim s1 As String
s1 = "www.manongku.com"
Debug.Print StrPtr(s1)
s1 = Left$(s1, 1)
Debug.Print StrPtr(s1)
s1 = "码农库"
Debug.Print StrPtr(s1)
End Sub
运行结果:
198742284
197567756
197570516
如果字符串缩小,地址也会变,不存在直接给字符串截取后加0而节约的情况。增加也会变。