关于VBA中RtlMoveMemory使用及变量内存地址的技术疑问
可用代码
Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSource As Any, ByVal ByteLen As Long) Sub Test() Dim Long1 As Long Dim Long2 As Long Long1 = 1000 CopyMemory ByVal VarPtr(Long2), ByVal VarPtr(Long1), 4 Debug.Print VarPtr(Long1) Debug.Print Long2 End Sub
疑问1:为什么每次运行程序时VarPtr(Long1)返回的地址始终相同?
这和VBA的局部变量内存分配逻辑直接相关:
Long1是过程级局部变量,这类变量是分配在栈内存而非堆内存中的。VBA运行时每次执行Test过程时,都会为该过程创建一个布局固定的栈帧——简单说,每个局部变量在栈帧里的相对位置是固定死的。- 只要你没有重启VBA的宿主程序(比如Excel、Word),整个进程的栈基址就不会改变,所以每次运行
Test时,Long1相对于栈基址的偏移量固定,最终的内存地址也就保持一致。 - 你关闭Chrome标签页释放的是堆内存,栈内存和堆内存是完全独立的内存区域,堆的变化不会影响栈上变量的地址分配。如果重启宿主程序再运行,你会发现这个地址大概率会变化,因为新进程的栈基址已经不同了。
疑问2:为什么使用CopyMemory时必须添加ByVal才能得到正确结果?
得结合VBA的参数传递规则和CopyMemory的核心作用来拆解:
- VBA默认参数传递是
ByRef:意思是传递的是变量的内存地址,而非变量的值。但VarPtr(Long1)返回的是一个表达式结果(Long1的内存地址,是个Long型数值),不是一个可被引用的持久变量。 - 不加
ByVal的问题:如果写CopyMemory VarPtr(Long2), VarPtr(Long1), 4,VBA会为VarPtr(Long2)和VarPtr(Long1)的结果各创建临时变量存储数值,再把这两个临时变量的地址传给CopyMemory。这时候CopyMemory只是把临时变量里的VarPtr(Long1)数值,写到另一个临时变量里,完全没碰Long2的实际内存,所以Long2的值还是初始的0。 ByVal的关键作用:当你写ByVal VarPtr(Long2)时,传递的是VarPtr(Long2)的数值本身(也就是Long2的内存地址),同理ByVal VarPtr(Long1)传递的是Long1的内存地址。这时候CopyMemory就会从Long1的地址读取4个字节(也就是Long1的值1000),写到Long2的地址指向的内存里,这样Long2就能正确拿到Long1的值了。- 显式
ByRef报错的原因:因为VarPtr()返回的是表达式,不是一个有持久内存地址的变量,VBA无法传递它的引用(毕竟临时变量在表达式计算完成后就会被销毁),所以显式指定ByRef会触发编译错误,提示参数无效。
内容的提问来源于stack exchange,提问作者Nicholas Humphrey
相关产品推荐
相关产品推荐

